Bitwise Operations in Go
Using bitwise operators in Go to manipulate individual flags within a single integer for compact state management.
In the below Go file we use bitwise operators to manipulate individual flags (on/off switches) in a single integer, where each bit position represents a different status.
First we’ll look at the code, and then we’ll explain how it works:
package main
import (
"fmt"
"strings"
)
// Define bit flags as constants, where each status is represented by a unique bit position
const (
StatusActive = 1 << iota // 1 << 0 which is 0001 (binary)
StatusAdmin // 1 << 1 which is 0010
StatusBanned // 1 << 2 which is 0100
StatusVerified // 1 << 3 which is 1000
)
// Stringify statuses for easier output
func stringifyStatus(status int) string {
statuses := []string{}
if status&StatusActive != 0 {
statuses = append(statuses, "Active")
}
if status&StatusAdmin != 0 {
statuses = append(statuses, "Admin")
}
if status&StatusBanned != 0 {
statuses = append(statuses, "Banned")
}
if status&StatusVerified != 0 {
statuses = append(statuses, "Verified")
}
return strings.Join(statuses, ", ")
}
func main() {
// Let's create a user status and use bitwise OR to combine different flags
var userStatus int
// Set the user as active and verified
userStatus |= StatusActive | StatusVerified
fmt.Println("Initial Status:", stringifyStatus(userStatus))
// Add "Admin" status
userStatus |= StatusAdmin
fmt.Println("After adding Admin:", stringifyStatus(userStatus))
// Remove "Verified" status using bitwise AND with NOT
userStatus &^= StatusVerified
fmt.Println("After removing Verified:", stringifyStatus(userStatus))
// Add "Banned" status
userStatus |= StatusBanned
fmt.Println("After adding Banned:", stringifyStatus(userStatus))
// Check if the user is an admin
if userStatus&StatusAdmin != 0 {
fmt.Println("User is an admin.")
} else {
fmt.Println("User is NOT an admin.")
}
// Remove "Admin" status using bitwise AND with NOT
userStatus &^= StatusAdmin
fmt.Println("After removing Admin:", stringifyStatus(userStatus))
// Check if the user is banned
if userStatus&StatusBanned != 0 {
fmt.Println("User is banned.")
} else {
fmt.Println("User is NOT banned.")
}
// Check if the user is an admin
if userStatus&StatusAdmin != 0 {
fmt.Println("User is an admin.")
} else {
fmt.Println("User is NOT an admin.")
}
}
Visualising Bits
In case you need a reminder of what bit alignment and shifting look like, take a
look at the following diagram: which shows a byte consists of 8 bits:
graph TD
%% Define Styles
classDef byteContainer fill:#f9f9f9,stroke:#333,stroke-width:2px;
classDef bitOn fill:#d4edda,stroke:#28a745,stroke-width:2px,color:#155724;
classDef bitOff fill:#e2e3e5,stroke:#6c757d,stroke-width:2px,color:#383d41;
classDef labelText fill:none,stroke:none,font-weight:bold;
style Byte fill:#fff,stroke:#333,stroke-width:2px;
style Total fill:#fff,stroke:#333,stroke-width:2px;
subgraph Byte [A Single Byte = 8 Bits]
direction TB
subgraph B0 [Bit 0]
v0["Value: 1<br>(ON)"]:::bitOn
d0["2^0 = 1<br>DESC: 2/2 = 1"]:::labelText
end
subgraph B1 [Bit 1]
v1["Value: 1<br>(ON)"]:::bitOn
d1["2^1 = 2<br>ASC: 2*1 = 2<br>DESC: 4/2 = 2"]:::labelText
end
subgraph B2 [Bit 2]
v2["Value: 0<br>(OFF)"]:::bitOff
d2["2^2 = 4<br>ASC: 2*2 = 4<br>DESC: 8/2 = 4"]:::labelText
end
subgraph B3 [Bit 3]
v3["Value: 1<br>(ON)"]:::bitOn
d3["2^3 = 8<br>ASC: 2*2*2 = 8<br>DESC: 16/2 = 8"]:::labelText
end
subgraph B4 [Bit 4]
v4["Value: 1<br>(ON)"]:::bitOn
d4["POWER: 2^4 = 16<br>ASC: 2*2*2*2 = 16<br>DESC: 32/2 = 16"]:::labelText
end
subgraph B5 [Bit 5]
v5["Value: 0<br>(OFF)"]:::bitOff
d5["2^5 = 32<br>ASC: 2*2*2*2*2 = 32<br>DESC: 64/2 = 32"]:::labelText
end
subgraph B6 [Bit 6]
v6["Value: 0<br>(OFF)"]:::bitOff
d6["2^6 = 64<br>ASC: 2*2*2*2*2*2 = 64<br>DESC: 128/2 = 64"]:::labelText
end
subgraph B7 [Bit 7]
v7["Value: 0<br>(OFF)"]:::bitOff
d7["2^7 = 128<br>ASC: 2*2*2*2*2*2*2 = 128<br>DESC: 256/2 = 128"]:::labelText
end
end
subgraph Total [if all bits 'enabled']
subgraph subtotal
128+64+32+16+8+4+2+1
d8["255"]:::labelText
end
end
%% Invisible link to force Total under Byte
Byte ~~~ Total
If diagrams aren’t your thing, then here’s a traditional image representation:
Think of a byte as a small control panel containing a row of eight individual
light switches, and each of those switches is a bit. A bit can only ever be in
one of two states: turned off (represented by a 0) or turned on (represented
by a 1). By flipping different combinations of these eight switches on and
off, a single byte can create 256 unique patterns. In computer engineering, we
use these distinct patterns to represent everything from letters and numbers to
specific status flags in a program.
ℹ️ INFO
You might wonder why we say a byte holds 256 values but the maximum is
255. The reason the sum of all bits equals 255 while 2^8 equals 256
comes down to how computers count. A single byte has eight bits, yielding
2^8 (or 256) total unique combinations of ones and zeros, meaning it
can store exactly 256 distinct values.
However, because digital systems must use the very first combination
(00000000) to represent the number 0, the remaining 255 combinations map
to the numbers 1 through 255. Mathematically, the sum of any binary
sequence is always exactly one less than the value of the next positional
power of two, meaning the maximum value of an 8-bit byte tops out at 256 - 1,
allowing it to span a range from 0 to 255.
Defining Bit Flags
You can utilize the bitwise OR assignment operator (|=) to selectively flip
these individual bit switches ‘ON’ (setting them to 1). We can then use this “bit
shifting” approach to combine multiple status flags within a single integer.
So for our example, each status was assigned a unique power of 2 using bit
shifting (1 << iota). This ensured each flag only affected a single bit:
StatusActivehas the binary value0001(1 << 0== 1 in decimal).StatusAdminhas the binary value0010(1 << 1== 2 in decimal).StatusBannedhas the binary value0100(1 << 2== 4 in decimal).StatusVerifiedhas the binary value1000(1 << 3== 8 in decimal).
Setting Statuses
The following example combines two separate status flags:
userStatus |= StatusActive | StatusVerified
ℹ️ INFO
In Go (and many other languages like C, C++, Java, and Python), |= is a
compound assignment operator. It combines a bitwise OR operation (|) with
an assignment operation (=).
So it’s a shorter way of writing:
userStatus = userStatus | StatusActive | StatusVerified
Which due to left-to-right evaluation associativity means:
userStatus = (userStatus | StatusActive) | StatusVerified
If you’re unsure of what associativity means:
In mathematics and logic, bitwise OR is associative. Just like addition
(2+3+4 is the same whether you do (2+3)+4 or 2+(3+4)), it doesn’t matter
which bits you combine first.
No matter how you group them, you are ultimately just taking all the 1 bits
from userStatus, StatusActive, and StatusVerified and smashing them
together into a single value.
In binary, this combination (0001 + 1000) results in 1001 (or 9 in
decimal; the earlier diagram/image showed a byte and its first bit is 1 and
its fourth bit is 8: 1+8 is 9), which means both the “active” and
“verified” flags are set.
Adding and Removing Flags
The following example sets the “admin” bit without affecting the other bits, resulting in 1011 (11 in decimal):
userStatus |= StatusAdmin
Comparing Statuses
Once userStatus has combined flags we can use the & operator to perform a bitwise AND operation, which means it compares each bit of two integers. For each bit position, if both bits are 1, the result at that position will be 1; otherwise, it will be 0.
So what happens when we compare userStatus&StatusAdmin != 0?
Well, StatusAdmin is a bit flag defined as 1 << 1, which results in 0010 in binary. This means that StatusAdmin occupies the second bit position in the binary representation of an integer. When we do userStatus&StatusAdmin, we’re effectively “masking” all bits except for the one represented by StatusAdmin (this is known as bit masking).
When we perform userStatus&StatusAdmin, we get a result where only the bit corresponding to StatusAdmin remains (and is set to 1 if that bit was already set in userStatus). If this result is non-zero (!= 0), it means the StatusAdmin bit is set in userStatus. If it’s zero, then StatusAdmin is not set in userStatus.
If we look at the code in bitwise.go we’ll see userStatus is initially set to include StatusActive and StatusVerified, so userStatus is 1001 in binary (which is 9 in decimal). Remember StatusActive occupied the first bit position (0001), while StatusVerified occupied the fourth bit position (1000) so if setting both flags we get the combined 1001.
Next, we add the StatusAdmin flag with userStatus |= StatusAdmin, making userStatus now 1011 in binary (which is 11 in decimal). When we check if StatusAdmin is set using userStatus&StatusAdmin != 0 we get back 2 from userStatus&StatusAdmin (which is 0010 in binary) because we’ve bit masked the other bits that might have been turned on (if you recall, using & turns each bit to zero except for those bits that were 1 in both numbers being compared), in order to reveal whether the StatusAdmin bit was set on or not (i.e. 0 != 2 so we know this person is an admin).
