Chapter 30: Bitwise
Bitwise operators.
Many beginners skip or fear bitwise operations because they look “low-level” or “math-y”, but in real Go code they appear surprisingly often — especially when you work with:
- flags / permissions
- network protocols
- file modes
- colors (RGB)
- crypto / hashing
- compression
- performance-critical code
- embedded / systems programming
Go has very clean and complete bitwise operators — actually one more than most C-family languages (&^ bit clear).
Let’s go through them slowly, like a patient teacher would do it in a live session — theory + visual binary examples + runnable code + common real-world patterns.
1. The Six Bitwise Operators in Go
| Operator | Name | What it does (bit by bit) | Example (x = 10 = 1010₂, y = 12 = 1100₂) | Result (binary) | Decimal result | Most common use |
|---|---|---|---|---|---|---|
| & | AND | 1 only if both bits are 1 | x & y | 1000 | 8 | Mask / keep bits |
|
OR | 1 if at least one bit is 1 | x |
y | 1110 | |
| ^ | XOR | 1 if bits are different | x ^ y | 0110 | 6 | Toggle / difference |
| &^ | AND NOT (bit clear) | Clear bits where right operand has 1 (Go-specific) | x &^ y | 0010 | 2 | Remove flags |
| << | Left shift | Shift bits left, fill with 0 on right | x << 2 | 101000 | 40 | Multiply by powers of 2 |
| >> | Right shift | Shift bits right, preserve sign (arithmetic shift) | y >> 1 | 0110 | 6 | Divide by powers of 2 |
2. Visual Understanding (Binary Table)
Let’s take two 8-bit numbers for clarity:
|
0 1 2 3 4 5 6 7 |
x = 10 = 00001010 y = 12 = 00001100 |
| Operation | Binary result | Decimal | Explanation |
|---|---|---|---|
| x & y | 00001000 | 8 | only positions where both 1 → bit 3 |
x |
y | 00001110 | 14 |
| x ^ y | 00000110 | 6 | positions where exactly one has 1 |
| x &^ y | 00000010 | 2 | x with all 1-bits from y cleared |
| x << 2 | 00101000 | 40 | shifted left 2 positions |
| y >> 1 | 00000110 | 6 | shifted right 1 position |
3. Runnable Examples — Copy-Paste & Run
Create bitwise_operators.go:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package main import "fmt" func bin(n int) string { return fmt.Sprintf("%08b", n) } func main() { x, y := 10, 12 // 00001010 and 00001100 fmt.Println("x =", x, bin(x)) fmt.Println("y =", y, bin(y)) fmt.Println("\nBitwise operations:") fmt.Printf("x & y = %2d %s (AND - keep common 1s)\n", x&y, bin(x&y)) fmt.Printf("x | y = %2d %s (OR - combine 1s)\n", x|y, bin(x|y)) fmt.Printf("x ^ y = %2d %s (XOR - toggle where different)\n", x^y, bin(x^y)) fmt.Printf("x &^ y = %2d %s (AND NOT - clear y's 1-bits from x)\n", x&^y, bin(x&^y)) fmt.Println("\nShifts:") fmt.Printf("x << 2 = %2d %s (left shift = multiply × 4)\n", x<<2, bin(x<<2)) fmt.Printf("y >> 1 = %2d %s (right shift = divide ÷ 2)\n", y>>1, bin(y>>1)) // Negative numbers (two's complement) neg := -10 // ...11110110 in two's complement fmt.Printf("\n-10 >> 2 = %d (arithmetic right shift preserves sign)\n", neg>>2) } |
4. Real-World Patterns You Will Actually Use
Pattern 1: Bit flags / permissions (most common use)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
const ( FlagRead = 1 << 0 // 0001 = 1 FlagWrite = 1 << 1 // 0010 = 2 FlagExecute = 1 << 2 // 0100 = 4 FlagDelete = 1 << 3 // 1000 = 8 ) var permissions int // Set flags permissions |= FlagRead | FlagWrite // add read + write // Check if has flag hasWrite := permissions&FlagWrite != 0 // Remove flag permissions &^= FlagDelete // Toggle flag permissions ^= FlagExecute |
Pattern 2: Colors (RGB)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const ( RedMask = 0xFF0000 GreenMask = 0x00FF00 BlueMask = 0x0000FF ) color := 0xFF5733 // #FF5733 red := (color & RedMask) >> 16 green := (color & GreenMask) >> 8 blue := color & BlueMask |
Pattern 3: Fast multiply / divide by powers of 2
|
0 1 2 3 4 5 6 7 8 |
n := 42 n <<= 3 // n = 42 × 8 = 336 n >>= 1 // n = 336 / 2 = 168 |
5. Common Beginner Mistakes
- Forgetting that << and >> have higher precedence than +/-
|
0 1 2 3 4 5 6 7 8 9 10 |
// Wrong precedence 1 << 2 + 3 // = 1 << (2+3) = 1 << 5 = 32 ← probably not what you wanted // Correct (1 << 2) + 3 // 4 + 3 = 7 |
- Shifting negative numbers right → sign bit preserved
|
0 1 2 3 4 5 6 |
-8 >> 1 // -4 (not big positive number) |
- Using ^ thinking it’s NOT (it’s XOR)
|
0 1 2 3 4 5 6 7 |
^5 // compile error — unary ^ is bitwise NOT, but needs parentheses or cast ^uint(5) // ^00000101 = 11111010 = -6 in two's complement |
6. Quick Practice – Try These
Predict the result (in decimal):
- 13 & 6 (1101 & 0110)
- 13 | 6
- 13 ^ 6
- 13 &^ 6
- 7 << 2
- 28 >> 2
Run them — which one surprised you most?
Any part still unclear?
- How &^ is different from & ~ in other languages?
- Bitwise tricks for checking if power of 2?
- When to prefer bit flags vs bool fields in structs?
- Or ready for pointer operators (& and *) next?
Keep playing with small numbers in binary — once you start seeing the bits, bitwise becomes intuitive and powerful.
You’re doing excellent work — keep asking! 💪🇮🇳🚀
