Bit Tricks (The Foundational Bit-Manipulation Toolkit) — Junior Level¶
One-line summary: Integers are just rows of bits, and with a handful of operators (
&,|,^,~,<<,>>) plus two magic identities (x & (x-1)clears the lowest set bit,x & -xisolates it) you can test, set, clear, toggle, and count bits in O(1) — the building blocks under flags, sets, hashing, and low-level performance code.
Table of Contents¶
- Introduction
- Prerequisites
- Glossary
- Core Concepts
- Big-O Summary
- Real-World Analogies
- Pros & Cons
- Step-by-Step Walkthrough
- Code Examples
- Coding Patterns
- Error Handling
- Performance Tips
- Best Practices
- Edge Cases & Pitfalls
- Common Mistakes
- Cheat Sheet
- Visual Animation
- Summary
- Further Reading
Introduction¶
Every integer your computer stores is, underneath, a fixed-width row of binary digits called bits. The number 13 in a 32-bit integer is really the row 00000000 00000000 00000000 00001101. Most of the time you never see those bits — you just do arithmetic. But a whole family of problems becomes dramatically simpler, faster, or smaller in memory once you start treating an integer as a row of switches you can flip individually.
That is what bit manipulation is: using the bitwise operators (AND, OR, XOR, NOT, and the two shifts) to read and change individual bits or groups of bits directly. The payoff shows up everywhere:
- Flags and options. Instead of ten separate boolean variables, pack ten on/off settings into one integer, one bit each.
- Sets of small things. A 64-bit integer is a ready-made set of the numbers
0..63; membership is oneAND, insertion is oneOR. - Speed. Multiplying by
8isx << 3; testing "is this even?" isx & 1; checking "is this a power of two?" isx & (x-1) == 0. These run in a single CPU cycle. - Algorithms. Counting set bits ("popcount"), finding the lowest set bit, and computing
log2via leading zeros are the engine behind hashing, compression, graphics, cryptography, and competitive-programming tricks.
This file teaches the foundational toolkit: how integers are stored (including the all-important two's complement for negative numbers), the core single-bit operations (test, set, clear, toggle), the two famous identities x & (x-1) and x & -x, the power-of-two check, counting bits, and finding leading/trailing zeros. We emphasize correctness above cleverness — bit tricks are notorious for subtle bugs around sign and width (32-bit vs 64-bit), and we will point at every trap as we go.
A single mental model carries the whole topic: the integer is a row of cells; each operator transforms that row in a predictable, parallel way. Once you can picture the cells, every trick becomes obvious instead of mysterious.
Prerequisites¶
Before reading this file you should be comfortable with:
- Binary numbers — that
1101₂ = 8 + 4 + 0 + 1 = 13, and how to convert small numbers to and from binary. - Integer types and their widths — that an
int32holds 32 bits, anint64/longholds 64, and that values wrap around when they exceed the width. - Boolean logic — AND is true only if both inputs are true; OR is true if either is; XOR is true if exactly one is.
- Powers of two —
1, 2, 4, 8, 16, …and that bit positionihas place value2^i. - Basic loops and functions — every algorithm here is a short loop or a one-liner.
Optional but helpful:
- A peek at hexadecimal (
0xFF = 11111111₂), because masks are far easier to read in hex. - The idea of signed vs unsigned integers, which we develop from scratch in Core Concepts.
Glossary¶
| Term | Meaning |
|---|---|
| Bit | A single binary digit, 0 or 1. The smallest unit of data. |
| Width | How many bits a value has (8, 16, 32, 64). Operations are defined within that width; going past it wraps. |
| LSB (least significant bit) | Bit position 0, the rightmost bit, place value 2^0 = 1. Decides odd/even. |
| MSB (most significant bit) | The leftmost bit. In a signed type it is the sign bit. |
| Mask | A constant whose set bits select which positions you want to act on, e.g. 1 << i. |
| Set / clear / toggle a bit | Force a bit to 1 / force it to 0 / flip it. |
| Set bit | A bit equal to 1 (also "on" bit). |
| Popcount | The number of set bits in a value (the population count or Hamming weight). |
| Two's complement | The standard way to represent signed integers; -x is stored as ~x + 1. |
| Sign bit | The MSB; 1 means negative under two's complement. |
| CLZ / CTZ | Count Leading Zeros / Count Trailing Zeros — number of 0 bits before the first 1 from the top / bottom. |
| Power of two | A number with exactly one set bit (1, 2, 4, 8, …). |
| Logical vs arithmetic shift | Right shift filling with 0 (logical) vs copying the sign bit (arithmetic). |
Core Concepts¶
1. The Six Operators¶
Bit manipulation rests on six operators. Picture two rows of bits lined up; each acts column by column.
| Operator | Symbol | Rule (per bit) | Use |
|---|---|---|---|
| AND | & | 1 only if both are 1 | clear/keep bits (masking) |
| OR | \| | 1 if either is 1 | set bits |
| XOR | ^ | 1 if exactly one is 1 | toggle bits, compare, swap |
| NOT | ~ | flips every bit | build inverse masks |
| Left shift | << | move bits left, fill 0 on right | multiply by 2^n, build masks |
| Right shift | >> | move bits right | divide by 2^n, extract fields |
A worked column example for 12 & 10:
2. Two's Complement (How Negatives Are Stored)¶
Computers do not store a separate "minus sign". Instead they use two's complement: to get -x, flip every bit and add 1.
The beautiful consequence: ordinary addition "just works" for negatives, and the single identity -x == ~x + 1 is the source of many tricks. The leftmost bit (the sign bit) is 1 for every negative number. Remember this — it is why x & -x (next section) works.
3. Single-Bit Operations With 1 << i¶
To act on bit i, build the mask 1 << i (a single 1 at position i):
Test bit i: (x >> i) & 1 → 1 if set, else 0
Set bit i: x | (1 << i) → forces bit i to 1
Clear bit i: x & ~(1 << i) → forces bit i to 0
Toggle bit i: x ^ (1 << i) → flips bit i
These are the four fundamental moves. Everything else is built from them.
4. The Two Magic Identities¶
Two expressions appear constantly:
x & (x - 1)clears the lowest set bit. Subtracting 1 flips the lowest 1 to 0 and turns all the 0s below it into 1s; ANDing with the original wipes out that whole run.
x & -xisolates the lowest set bit. Because-x = ~x + 1, the only bit that survives the AND is the lowest 1.
5. Power-of-Two Check¶
A power of two has exactly one set bit. Clearing its lowest set bit leaves zero:
The x > 0 guard matters: 0 and negative numbers must be rejected.
6. Counting Set Bits (Popcount)¶
The number of 1-bits is the popcount. The simplest correct loop uses the clear-lowest-bit identity (Brian Kernighan's method): each iteration removes one set bit, so it loops exactly popcount times.
7. Leading / Trailing Zeros and log2¶
- CTZ (count trailing zeros) = position of the lowest set bit.
- CLZ (count leading zeros) = number of zeros above the highest set bit.
- For a positive number,
floor(log2(x)) = (width - 1) - clz(x)— the index of the highest set bit.
Most languages give you these as fast builtins (covered in Code Examples).
8. Multiply / Divide by Powers of Two¶
Shifting moves the place values: x << k multiplies by 2^k, and x >> k divides by 2^k (rounding toward zero for unsigned or non-negative values; signed right shift rounds toward negative infinity, a pitfall we flag later).
Big-O Summary¶
| Operation | Time | Notes |
|---|---|---|
| Test / set / clear / toggle one bit | O(1) | A shift plus one logical op. |
x & (x-1) (clear lowest set bit) | O(1) | One subtract + one AND. |
x & -x (isolate lowest set bit) | O(1) | One negate + one AND. |
| Power-of-two check | O(1) | One subtract, one AND, one compare. |
| Popcount via Kernighan loop | O(set bits) | At most width iterations; usually fewer. |
| Popcount via builtin / parallel | O(1) | Hardware instruction or fixed shifts. |
| CLZ / CTZ via builtin | O(1) | Hardware instruction on modern CPUs. |
| CLZ / CTZ via naive loop | O(width) | Scans bit by bit. |
Multiply / divide by 2^k (shift) | O(1) | Single instruction. |
| Round up to next power of two | O(1) | Fixed sequence of shifts/ORs (or one CLZ). |
The headline: almost everything is O(1) within a fixed width. The only loop is the bit-by-bit fallback, bounded by the width (≤ 64). That constant-time nature is exactly why these tricks power performance-critical code.
Real-World Analogies¶
| Concept | Analogy |
|---|---|
| Integer as bits | A row of light switches; each switch is one bit, on (1) or off (0). |
Mask (1 << i) | A stencil that exposes only one switch so you can flip exactly it. |
| AND with a mask | Putting a cardboard cutout over the row so only the selected switches show through. |
| OR to set | Pressing a switch firmly on — it ends up on no matter where it started. |
| XOR to toggle | A push-button light switch: press it and it changes state. |
| Two's complement | An odometer that rolls backward past zero into "all nines" instead of showing a minus sign. |
x & (x-1) | Removing the rightmost lit bulb from a string of fairy lights. |
| Popcount | Counting how many bulbs in the string are currently lit. |
| Power of two | A light string with exactly one bulb on. |
Where the analogies break: real switches do not "carry" into their neighbor, but subtraction (x - 1) does ripple across a run of zeros. That ripple is precisely what makes x & (x-1) work.
Pros & Cons¶
| Pros | Cons |
|---|---|
| Constant-time operations; often a single CPU instruction. | Code is terse and hard to read without comments. |
| Compact storage — 64 booleans fit in one 64-bit word. | Easy to introduce sign/width bugs that pass small tests. |
| No branches in many tricks → CPU-pipeline friendly. | Tricks differ between signed and unsigned types. |
Portable core (&, |, ^, shifts) across all languages. | Shifting by ≥ the width is undefined behavior in C/C++/Go. |
| Foundation for sets, flags, hashing, compression, graphics. | XOR swap and other "clever" tricks are footguns in real code. |
Builtins (popcount, clz, ctz) make it fast and clean. | Misreading a hex mask silently corrupts results. |
When to use: flags/options bundles, small-universe sets, performance-critical inner loops, hashing and checksums, low-level protocols, and counting/searching problems where O(1) bit math replaces a loop.
When NOT to use: when readability matters more than the nanoseconds saved, when the "set" is large or sparse (use a real hash set), or when a clear arithmetic expression (x * 8) is just as fast and far clearer than x << 3 to your team.
Step-by-Step Walkthrough¶
Let us take x = 44 and walk through the core operations by hand. In 8 bits, 44 = 00101100.
Test bit 2. (44 >> 2) & 1. Shifting right by 2 gives 00001011; AND with 1 gives 1. Bit 2 is set. ✓ (Indeed 44 includes 4 = 2^2.)
Set bit 1. 44 | (1 << 1) = 00101100 | 00000010 = 00101110 = 46. Bit 1 is now on.
Clear bit 2. 44 & ~(1 << 2). The mask 1 << 2 = 00000100; its inverse ~ is 11111011; AND gives 00101000 = 40. Bit 2 is now off.
Toggle bit 5. 44 ^ (1 << 5) = 00101100 ^ 00100000 = 00001100 = 12. Bit 5 was on, now off.
Clear the lowest set bit, x & (x-1):
x = 00101100 (44)
x - 1 = 00101011 (43)
x&(x-1)= 00101000 (40) ← the lowest set bit (position 2) vanished
Isolate the lowest set bit, x & -x:
x = 00101100 (44)
-x = 11010100 (two's complement of 44)
x & -x = 00000100 (4) ← exactly the lowest set bit, at position 2
Count set bits with Kernighan's loop:
44 = 00101100 → 3 set bits expected
iter 1: x = 44 & 43 = 40, count = 1
iter 2: x = 40 & 39 = 32, count = 2
iter 3: x = 32 & 31 = 0, count = 3
x is 0 → stop. popcount(44) = 3 ✓
Three iterations for three set bits — the loop runs exactly popcount times, never more.
Code Examples¶
Example: The Core Toolkit in Three Languages¶
Each snippet implements test/set/clear/toggle, the two identities, power-of-two, and popcount.
Go¶
package main
import (
"fmt"
"math/bits"
)
func testBit(x uint, i int) bool { return (x>>uint(i))&1 == 1 }
func setBit(x uint, i int) uint { return x | (1 << uint(i)) }
func clearBit(x uint, i int) uint { return x &^ (1 << uint(i)) } // &^ is AND-NOT
func toggleBit(x uint, i int) uint { return x ^ (1 << uint(i)) }
func lowestSetBit(x uint) uint { return x & (-x) } // isolate lowest 1
func dropLowestBit(x uint) uint { return x & (x - 1) } // clear lowest 1
func isPowerOfTwo(x uint) bool { return x != 0 && x&(x-1) == 0 }
func popcountKernighan(x uint) int {
count := 0
for x != 0 {
x &= x - 1 // drop one set bit per iteration
count++
}
return count
}
func main() {
x := uint(44) // 00101100
fmt.Println("test bit 2:", testBit(x, 2)) // true
fmt.Println("set bit 1:", setBit(x, 1)) // 46
fmt.Println("clear bit 2:", clearBit(x, 2)) // 40
fmt.Println("toggle bit 5:", toggleBit(x, 5)) // 12
fmt.Println("lowest set bit:", lowestSetBit(x)) // 4
fmt.Println("drop lowest:", dropLowestBit(x)) // 40
fmt.Println("power of two? 64:", isPowerOfTwo(64)) // true
fmt.Println("popcount:", popcountKernighan(x)) // 3
fmt.Println("builtin popcount:", bits.OnesCount(x)) // 3
}
Java¶
public class BitTricks {
static boolean testBit(int x, int i) { return ((x >> i) & 1) == 1; }
static int setBit(int x, int i) { return x | (1 << i); }
static int clearBit(int x, int i) { return x & ~(1 << i); }
static int toggleBit(int x, int i) { return x ^ (1 << i); }
static int lowestSetBit(int x) { return x & (-x); }
static int dropLowestBit(int x) { return x & (x - 1); }
static boolean isPowerOfTwo(int x) { return x > 0 && (x & (x - 1)) == 0; }
static int popcountKernighan(int x) {
int count = 0;
while (x != 0) {
x &= x - 1; // drop one set bit
count++;
}
return count;
}
public static void main(String[] args) {
int x = 44; // 00101100
System.out.println("test bit 2: " + testBit(x, 2)); // true
System.out.println("set bit 1: " + setBit(x, 1)); // 46
System.out.println("clear bit 2: " + clearBit(x, 2)); // 40
System.out.println("toggle bit 5: " + toggleBit(x, 5)); // 12
System.out.println("lowest set bit: " + lowestSetBit(x)); // 4
System.out.println("power of two? 64: " + isPowerOfTwo(64)); // true
System.out.println("popcount: " + popcountKernighan(x)); // 3
System.out.println("builtin popcount: " + Integer.bitCount(x)); // 3
}
}
Python¶
def test_bit(x, i): return (x >> i) & 1 == 1
def set_bit(x, i): return x | (1 << i)
def clear_bit(x, i): return x & ~(1 << i)
def toggle_bit(x, i): return x ^ (1 << i)
def lowest_set_bit(x): return x & (-x) # isolate lowest 1
def drop_lowest(x): return x & (x - 1) # clear lowest 1
def is_power_of_two(x): return x > 0 and (x & (x - 1)) == 0
def popcount_kernighan(x):
count = 0
while x:
x &= x - 1 # drop one set bit per iteration
count += 1
return count
if __name__ == "__main__":
x = 44 # 0b101100
print("test bit 2:", test_bit(x, 2)) # True
print("set bit 1:", set_bit(x, 1)) # 46
print("clear bit 2:", clear_bit(x, 2)) # 40
print("toggle bit 5:", toggle_bit(x, 5)) # 12
print("lowest set bit:", lowest_set_bit(x)) # 4
print("power of two? 64:", is_power_of_two(64)) # True
print("popcount:", popcount_kernighan(x)) # 3
print("builtin popcount:", x.bit_count()) # 3 (Python 3.10+)
What it does: runs every core trick on x = 44 and prints results that match the hand walkthrough. Run: go run main.go | javac BitTricks.java && java BitTricks | python bittricks.py
Note on Python: Python integers are arbitrary precision — there is no fixed width and no two's-complement sign bit, so
~x == -x-1and there is no 32-bit overflow. When a problem assumes 32-bitint, mask with& 0xFFFFFFFFto simulate it. This is the single biggest cross-language gotcha for beginners.
Coding Patterns¶
Pattern 1: Build a Mask for the Low i Bits¶
Intent: Get a value whose lowest i bits are all 1, useful for extracting fields.
1 << i is a single 1 at position i; subtracting 1 turns it into i ones below that position.
Pattern 2: Extract a Bit Field¶
Intent: Read bits [lo, lo+len) out of x as a small number.
Shift the field down to position 0, then mask off everything above it.
Pattern 3: Iterate Over Set Bits¶
Intent: Visit each set bit's position exactly once (great for subset/bitmask problems).
def each_set_bit(x):
while x:
low = x & (-x) # isolate lowest set bit
i = low.bit_length() - 1 # its position
yield i
x &= x - 1 # drop it and continue
Pattern 4: Round Up to the Next Power of Two¶
Intent: Common in hash-table and buffer sizing. Smear the highest bit downward, then add 1.
def next_power_of_two_32(x):
if x <= 1:
return 1
x -= 1
x |= x >> 1
x |= x >> 2
x |= x >> 4
x |= x >> 8
x |= x >> 16
return x + 1
Error Handling¶
| Error | Cause | Fix |
|---|---|---|
Negative or huge result from 1 << i | i ≥ width of the type (undefined / overflow). | Keep 0 ≤ i < width; use a 64-bit literal 1L << i / uint64(1) << i if you need high bits. |
isPowerOfTwo(0) returns true | Forgot the x > 0 guard; 0 & -1 == 0. | Always guard: x > 0 && (x & (x-1)) == 0. |
Wrong sign after >> | Arithmetic (sign-preserving) shift on a negative number. | Use unsigned types or Java's >>> for a logical shift. |
| Python answer differs from Go/Java | Python has no fixed width / no sign bit. | Mask with & 0xFFFFFFFF (or 0xFFFFFFFFFFFFFFFF) to emulate fixed width. |
1 << 31 is negative in Java | 1 is a 32-bit int; bit 31 is the sign bit. | Use 1L << 31 for a 64-bit value, or treat as unsigned. |
x & -x of 0 is 0 | 0 has no set bit to isolate. | Handle x == 0 separately if 0 is possible. |
Performance Tips¶
- Prefer the builtin popcount (
bits.OnesCount,Integer.bitCount,int.bit_count) over hand loops — it compiles to a single CPU instruction on modern hardware. - Use
&^in Go (AND-NOT) to clear bits in one operator instead of& ~mask. - Avoid branches where a bit trick removes them:
x & 1beatsx % 2 == 0for parity, and branchlessmin/absavoid mispredictions in tight loops. - Shift to multiply/divide by powers of two only when the constant is genuinely a power of two; modern compilers already turn
x * 8into a shift, so do it for clarity, not magic. - Pick the smallest correct width. Operating on
uint32is fine for 32-bit data; do not promote to 64-bit unless you need the extra bits.
Best Practices¶
- Comment every mask with its meaning and the bit positions it touches;
0x0Fis far clearer as// low nibble. - Use unsigned types for pure bit work so that
>>is logical andx & -xbehaves predictably. - Prefer named helpers (
setBit,clearBit) over inline magic in application code; reserve raw operators for hot inner loops. - Reach for builtins for popcount/clz/ctz instead of reimplementing — they are correct, fast, and obvious.
- Test against a brute-force oracle (a bit-by-bit loop) for every clever trick before trusting it.
- Write the width down. Decide 32 vs 64 up front and stay consistent; mixing widths is where most bugs live.
Edge Cases & Pitfalls¶
x = 0— popcount is 0,x & -xis 0,x & (x-1)is 0, and it is not a power of two.- Shifting by
widthor more — undefined behavior in C/C++/Go and a no-op-or-wrap in some languages; never do1 << 32on a 32-bit type. - The sign bit —
1 << 31is negative as a signed 32-bitint; use a 64-bit or unsigned type for high bits. - Signed right shift —
-8 >> 1is-4(rounds toward negative infinity), not-4's magnitude truncation you might expect; division by shifting differs from integer division for negatives. - Negative numbers in
x & (x-1)— works fine in two's complement, but the "lowest set bit" of a negative number may surprise you; reason in binary, not in decimal. - Python's unbounded integers —
~xand<<never overflow, so 32-bit assumptions must be enforced manually with masks. -xof the minimum value — forINT_MIN,-xoverflows to itself;x & -xstill works but the magnitude wraps.
Common Mistakes¶
- Forgetting the
x > 0guard in the power-of-two check, so0falsely reports true. - Shifting
1instead of1L/uint64(1)when you need bit 32 or higher, silently truncating to 0 or hitting the sign bit. - Confusing
&with&&(and|with||) — the bitwise vs logical operators are different and mix-ups compile but misbehave. - Assuming
>>zero-fills on signed types; it copies the sign bit, corrupting negative-number math. - Porting C bit tricks to Python verbatim and ignoring that Python has no fixed width — results diverge for high bits and negatives.
- Using XOR swap in real code (
a ^= b; b ^= a; a ^= b) — it breaks when both operands alias the same location, setting it to 0. Use a temp or tuple swap. - Off-by-one in masks —
(1 << i)is one bit ati, while(1 << i) - 1isibits; mixing them produces fields that are too wide or too narrow.
Cheat Sheet¶
| Goal | Expression |
|---|---|
Test bit i | (x >> i) & 1 |
Set bit i | x \| (1 << i) |
Clear bit i | x & ~(1 << i) |
Toggle bit i | x ^ (1 << i) |
| Isolate lowest set bit | x & -x |
| Clear lowest set bit | x & (x - 1) |
| Is power of two? | x > 0 && (x & (x-1)) == 0 |
Low i bits mask | (1 << i) - 1 |
Extract field [lo, lo+len) | (x >> lo) & ((1 << len) - 1) |
Multiply by 2^k | x << k |
Divide by 2^k (non-negative) | x >> k |
| Popcount (builtin) | bits.OnesCount / Integer.bitCount / x.bit_count() |
| Trailing zeros (ctz) | bits.TrailingZeros / Integer.numberOfTrailingZeros |
| Leading zeros (clz) | bits.LeadingZeros / Integer.numberOfLeadingZeros |
floor(log2 x) (x>0) | (width-1) - clz(x) |
Core popcount loop (Kernighan):
count = 0
while x != 0:
x = x & (x - 1) # drop one set bit
count += 1
# runs exactly popcount(x) times
Visual Animation¶
See
animation.htmlfor an interactive visualization.The animation demonstrates: - The 32 bit cells of an editable integer, lit (1) or dark (0) - Stepping through set / clear / toggle on a chosen bit
i-x & (x-1)removing the lowest set bit, andx & -xisolating it - Brian Kernighan's popcount loop, one set bit removed per step - Play / Pause / Step controls and a live binary + decimal readout
Summary¶
A computer integer is a fixed-width row of bits, and the six operators (&, |, ^, ~, <<, >>) let you read and rewrite those bits in O(1). The four fundamental moves — test, set, clear, toggle — all build on the mask 1 << i. Negative numbers use two's complement (-x == ~x + 1), which powers the two identities you will use forever: x & (x-1) clears the lowest set bit (basis of Kernighan's popcount) and x & -x isolates it (basis of ctz). From these you get the power-of-two check, mask building, field extraction, and log2-via-clz. The whole toolkit is fast and compact, but it is unforgiving about sign and width: keep shifts in range, watch the sign bit, reach for unsigned types, prefer builtins over clever reimplementations, and remember that Python's unbounded integers do not behave like fixed-width ones. Master these basics and the rest of bit manipulation becomes routine.
Further Reading¶
- Hacker's Delight (Henry S. Warren, Jr.) — the canonical catalog of bit tricks with proofs.
- Sean Eron Anderson — "Bit Twiddling Hacks" (Stanford graphics page), a famous free reference.
- Introduction to Algorithms (CLRS) — appendix on integer arithmetic and representation.
- Go
math/bits, JavaInteger/Long, Pythonintdocumentation — the standard-library builtins. - Sibling topics in
18-bit-manipulation— bitmask DP, subset enumeration, and Gray codes build directly on this toolkit.