Bitwise Operations in Go

Go programming language code with gopher mascot

Go gives you five bitwise operators — but one of them, ^, doubles as both XOR and NOT. This guide covers all of them, plus the &^ (AND NOT) operator you won't find in C, iota-powered bitmask patterns, and what to watch for with signed vs. unsigned integer types.

Go code on a dark editor typing bitwise operations for systems programming
Go's Five Operators AND NOT (&^) iota Bitmask Pattern Signed vs Unsigned Shifts math/bits Package FAQ

Go's Five Bitwise Operators

Go has & (AND), | (OR), ^ (XOR as binary, NOT as unary), &^ (AND NOT / bit clear), << (left shift), and >> (right shift). The ^ doubling as XOR and NOT throws people coming from C or Python. In C, ~ is NOT and ^ is XOR. Go merged them because the compiler can tell which you mean by the number of operands.

package main

import "fmt"

func main() {
    a := uint8(0b1100)  // 12
    b := uint8(0b1010)  // 10

    fmt.Printf("a & b  = %04b (%d)\n", a&b, a&b)   // 1000 (8)
    fmt.Printf("a | b  = %04b (%d)\n", a|b, a|b)   // 1110 (14)
    fmt.Printf("a ^ b  = %04b (%d)\n", a^b, a^b)   // 0110 (6) — XOR
    fmt.Printf("^a     = %08b (%d)\n", ^a, ^a)     // 11110011 (243) — NOT
    fmt.Printf("a << 1 = %04b (%d)\n", a<<1, a<<1) // 11000 (24)
    fmt.Printf("a >> 1 = %04b (%d)\n", a>>1, a>>1) // 110 (6)
}

The output of ^a on a uint8 is 243 because NOT flips all 8 bits of the type. If a were an int (signed), ^a would be -13 because Go's signed NOT follows two's complement: ^x == -x - 1.

AND NOT (&^) — Go's Bit-Clear Operator

One operator you do not see in most other languages: &^, pronounced "AND NOT" or "bit clear." It clears every bit in the left operand that is set in the right operand. Mathematically, a &^ b is the same as a & (^b).

// a &^ b: clear bits in a where b has 1s
a := uint8(0b1111)  // 15 — all 4 bits set
b := uint8(0b0101)  // 5  — bits 0 and 2 set

result := a &^ b
fmt.Printf("%04b\n", result)  // 1010 (10)

// Bit-by-bit:
// a:     1111
// b:     0101
// clear: 1010  — bits 0 and 2 cleared because b had them set

I reach for &^ when I need to disable a flag in a bitfield without touching other flags. Instead of writing flags & ^FLAG_AUDIT, you write flags &^ FLAG_AUDIT. It reads cleaner once your eye adjusts to it.

const (
    FlagRead  = 1 << iota
    FlagWrite
    FlagExec
)

flags := FlagRead | FlagWrite | FlagExec  // 111 = 7

// Disable write permission
flags &^= FlagWrite
fmt.Printf("%03b\n", flags)  // 101 (5)

The iota Bitmask Pattern

Go's iota identifier auto-increments inside a const block, making it perfect for defining bitmask constants. Combine it with left shift and each constant gets exactly one bit. This is how Go's standard library defines file mode bits in the os package and network flags in net.

type Permission uint8

const (
    PermRead    Permission = 1 << iota  // 1 (bit 0)
    PermWrite                           // 2 (bit 1)
    PermExec                            // 4 (bit 2)
    PermAdmin                           // 8 (bit 3)
)

func has(p Permission, flag Permission) bool {
    return p&flag != 0
}

func main() {
    p := PermRead | PermWrite | PermAdmin  // 11
    fmt.Println(has(p, PermRead))   // true
    fmt.Println(has(p, PermExec))   // false
}

This pattern scales cleanly. Need 64 flags? Switch the underlying type to uint64. The rest of the code stays the same. The Go compiler catches accidental mixing of different flag types because Permission is a named type, not a bare integer.

Signed vs. Unsigned: Why Type Choice Matters

Go's right shift behavior depends entirely on whether you use a signed or unsigned integer type. This is not a runtime decision — the type system decides it at compile time.

// Unsigned: logical shift (zero-fill)
var u uint8 = 0b10000000  // 128
fmt.Printf("%08b\n", u>>1) // 01000000 (64)

// Signed: arithmetic shift (sign-fill)
var s int8 = -128  // Same bit pattern: 10000000
fmt.Printf("%08b\n", uint8(s>>1)) // 11000000 (-64)

The rule of thumb: if your bits represent actual data (flags, packed fields, network headers), use unsigned types. The shift behavior is predictable and you never get sign extension surprises. If the value you are manipulating is a genuine signed integer where negative values matter, use signed types and let the arithmetic shift preserve the sign.

Also note that shifting by a value greater than or equal to the type's bit width is undefined behavior at runtime. On most Go compilers this wraps using only the low bits of the shift count, but you should not rely on it.

The math/bits Package

Go 1.9 added math/bits, a standard library package with optimized bit-counting functions. Each function compiles to a single CPU instruction on hardware that supports it (POPCNT, LZCNT, etc.).

import "math/bits"

func main() {
    x := uint(0b11010010)

    fmt.Println(bits.OnesCount(x))    // 4 — population count
    fmt.Println(bits.Len(x))          // 8 — minimum bits to represent x
    fmt.Println(bits.LeadingZeros(x)) // number of leading zeros in uint
    fmt.Println(bits.TrailingZeros(x))// 1 — trailing zero bits

    // Rotate left by 2 positions
    rotated := bits.RotateLeft8(uint8(x), 2)
    fmt.Printf("%08b\n", rotated)     // 01001011
}

I use bits.OnesCount a lot when counting set flags in a bitmask. It is much faster than looping through bits manually, and the code is clearer.

Frequently Asked Questions

What is Go's bitwise NOT operator?

Go uses ^ as both XOR and unary NOT. As a binary operator, ^ is XOR (e.g., a ^ b). As a unary operator, ^ is bitwise NOT (e.g., ^a flips all bits). Go also has &^ (AND NOT), which clears bits: a &^ b clears all bits in a that are set in b. This is equivalent to a & (^b).

Does Go have signed and unsigned right shift?

Go has only one right shift operator (>>), and its behavior depends on the operand type. For uint types, it is a logical shift (fills with zeros). For int types, it is an arithmetic shift (propagates the sign bit). There is no separate >>> operator like in JavaScript or Java.

How do I use iota for bitmask constants in Go?

iota combined with bit shifting creates clean bitmask constants: type Flags uint8; const (FlagRead Flags = 1 << iota; FlagWrite; FlagExec). Each constant automatically gets the next bit position. This pattern is used in Go standard library packages like os (file mode bits) and net (interface flags).

Related Tools

Bitwise Calculator →

AND, OR, XOR, NOT, shifts

Bitwise Guide →

Complete operations reference

Reference →

Quick lookup tables

C Bitwise →

Low-level bit ops

Try These Operations Live

Experiment with AND, OR, XOR, and shift operations using our interactive bitwise calculator. See the binary, hex, and decimal output in real time — no compiler needed.