Bit Manipulation Field Notes
Bits, the five operators, shifting, and the tricks built on them: count the 1s, clear the lowest bit, cancel pairs with XOR, use an integer as a set. Every diagram shows the bits as boxes so you can see what moved.
0Why bits are worth one lesson
A row of eight light switches on a wall. Each switch is either up (1) or down (0). Nothing else. Every number, letter, and pixel in a computer is just a long row of these switches. Bit manipulation is the skill of reading and flipping the switches directly, instead of asking the computer to do arithmetic for you.
NeetCode puts this lesson last on purpose. It is not an algorithm and not a data structure. It is a CS concept: a set of tricks that make some algorithms simpler or faster. These questions are less common in interviews than arrays or trees, but they show up, and they are hard to solve if you do not know the trick. You either know it or you do not. So learn the five operators and the four patterns below, and you are covered.
Recall from the first chapter: a bit (binary digit) is the most fundamental unit a computer uses to represent data. It holds 0 or 1. Eight bits make a byte. An int in most languages is 32 or 64 bits.
1Reading and writing binary
Binary to decimal and back
In decimal (base 10) each position is a power of 10: ones, tens, hundreds. In binary (base 2) each position is a power of 2. Counting from the right: 1, 2, 4, 8, 16, 32, ... So 001 is 1, 010 is 2, 100 is 4, and 10111 is 16 + 4 + 2 + 1 = 23. This one rule converts any binary number to decimal.
Going the other way, keep dividing by 2 and write down the remainders from last to first. 23 → 11 r1 → 5 r1 → 2 r1 → 1 r0 → 0 r1. Reading the remainders backwards: 10111. You will see this exact division in the countBits loop below, because n >> 1 is n // 2.
| Binary | Place values that are on | Decimal |
|---|---|---|
001 | 1 | 1 |
010 | 2 | 2 |
100 | 4 | 4 |
0111 | 4 + 2 + 1 | 7 |
1000 | 8 | 8 |
10110 | 16 + 4 + 2 | 22 |
10111 | 16 + 4 + 2 + 1 | 23 |
11111111 | 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 | 255 |
Python does the conversion for you
Three tools. bin() turns an int into a string that starts with 0b. int(s, 2) parses a binary string. format (or an f-string) gives you a padded string for printing traces. You can also type a binary literal directly with the 0b prefix.
bin(23) # '0b10111' (a string, prefix 0b)
bin(23)[2:] # '10111' (drop the prefix)
format(23, "08b") # '00010111' (pad to 8 places: the byte in the picture)
f"{23:b}" # '10111'
int("10111", 2) # 23 (string in base 2 → int)
int("00010111", 2) # 23 (leading zeros are fine)
0b10111 # 23 (binary literal, no quotes)
bin(n) is for looking at a number. The number itself is always stored as bits; there is no "decimal int" and "binary int" in Python. 23 and 0b10111 are the same object.
2The five operators and their truth tables
A truth table lists every input combination and the output. For two bits there are only four combinations, so each table has four rows. The operators work on whole numbers by applying the table to each bit position separately: bit 0 with bit 0, bit 1 with bit 1, and so on.
- AND
&: both bits must be 1 to get 1; otherwise 0. - OR
|: only one bit has to be 1 to get 1. Also 1 if both are 1. 0 only if both are 0. - XOR
^(exclusive or): 1 if only ONE of the bits is 1; otherwise 0. So1 ^ 1 = 0. Think "are they different?". - NOT
~(negation): flips the bit.~0 = 1,~1 = 0. It takes one input, not two. - Shift
<<left,>>right: slides every bit one position. Left shift multiplies by 2 (doubling). Right shift divides by 2 (halving).
1 ^ 1 = 0. That single row is why XOR can cancel pairs, which is the trick in section 5.The same operators in Python
This is the lesson's code, line for line. Every line here was run.
# AND
n = 1 & 1 # 1
# OR
n = 1 | 0 # 1
# XOR
n = 0 ^ 1 # 1
# NOT (negation)
n = ~n # -2 (why not 0? see section 4: ~n is -n - 1 in Python)
# Bit shifting
n = 1
n = n << 1 # 2 (001 → 010, doubled)
n = n >> 1 # 1 (010 → 001, halved)
Left shift: slide left, a 0 comes in, the top bit can fall off
Start with 001 (decimal 1) in a 3-bit register and shift left three times. In the last step the 1 is truncated: it falls off the end and is replaced by 0. There is no room to keep it.
001 << 1 = 010 # 1 → 2
010 << 1 = 100 # 2 → 4
100 << 1 = 000 # 4 → 0 the 1 fell off the left edge
Right shift: slide right, the lowest bit falls off, the value halves
Right shift is the mirror. Every bit moves one box to the right, so every place value halves: the 16 becomes an 8, the 4 becomes a 2. The bit in the 1s box has nowhere to go and is dropped. That dropped bit is exactly the remainder of dividing by 2, which is why n >> 1 equals n // 2.
k divides by 2**k; n >> 3 is n // 8. Likewise n << k is n * 2**k.In Python, + and - are evaluated before << and >>. So 1 << k + 1 means 1 << (k + 1), not (1 << k) + 1. Put parentheses around every shift that sits next to arithmetic.
3Demonstration: count the 1 bits in 23
The lesson's worked example. 23 in binary is 10111. The plan: look at the lowest bit, then shift right so the next bit becomes the lowest, and repeat until all bits are 0.
How do we look at the lowest bit? With AND. Because 1 & 1 = 1 and 0 & 1 = 0, the expression n & 1 keeps only bit 0 and zeroes everything else. If n & 1 == 1, the lowest bit is on and we increment count. Then n = n >> 1 throws that bit away.
# Counting Bits
def countBits(n):
count = 0
while n > 0:
if n & 1 == 1: # is the lowest bit a 1?
count += 1
n = n >> 1 # same as n // 2: drop the lowest bit
return count
countBits(23) # 4
while n > 0 stops. The loop runs once per bit in the number, about log2(n) times, not n times.| Turn | n | n & 00001 | Lowest bit | count | n >> 1 → |
|---|---|---|---|---|---|
| 1 | 10111 (23) | 00001 | 1 | 1 | 01011 (11) |
| 2 | 01011 (11) | 00001 | 1 | 2 | 00101 (5) |
| 3 | 00101 (5) | 00001 | 1 | 3 | 00010 (2) |
| 4 | 00010 (2) | 00000 | 0 | 3 (just shift) | 00001 (1) |
| 5 | 00001 (1) | 00001 | 1 | 4 | 00000 (0), loop ends |
Count = 4. Check it against the byte picture in section 0: 23 has 1s in the 16, 4, 2 and 1 boxes. Four boxes.
The one-liners Python gives you
In real code use the built-ins. In an interview, write the loop first, then mention these.
bin(23).count("1") # 4 works on every Python version
(23).bit_count() # 4 Python 3.10+, fastest; note the parentheses around the literal
(23).bit_length() # 5 how many bits it takes to write 23 (10111)
The number of 1 bits has a name: the popcount or Hamming weight. LeetCode 191 asks for exactly this. Section 5 shows a second way to count that skips the 0 bits entirely.
4Python ints, negatives, and masks
Python ints do not overflow
In C, Java, or on the CPU, an int has a fixed width (32 or 64 bits). Shift a 1 past the top and it is gone, as in the 3-bit register above. Python ints are arbitrary precision: they grow as many bits as the value needs. 1 << 100 is simply 2100, a 31-digit number. Nothing falls off on the left. The only bits Python ever drops are the ones you push off the right with >>.
1 << 3 # 8, not 0: Python has no 3-bit register
1 << 100 # 1267650600228229401496703205376
(1 << 100) > 0 # True, no wrap-around to negative
# when a problem says "32-bit", cut to 32 bits yourself:
MASK = 0xFFFFFFFF # 32 ones
(1 << 35) & MASK # 0: bits above 31 removed
Why ~n is -n - 1
Hardware stores negative numbers in two's complement: to get -x in a fixed width, flip every bit of x and add 1. The top bit then acts as the sign. In 8 bits, 5 is 00000101, flipping gives 11111010, adding 1 gives 11111011, which is -5. Python behaves as if the number had infinitely many sign bits on the left, so ~n (flip every bit) is always -n - 1: ~0 == -1, ~1 == -2, ~23 == -24. That is why the lesson's n = ~n on 1 gives -2, not 0.
~0 # -1
~1 # -2
~23 # -24
bin(-5) # '-0b101' Python prints sign + magnitude, not the bit pattern
(-5) & 0xFF # 251 = 0b11111011 the 8-bit two's complement pattern
-8 >> 1 # -4 right shift keeps the sign (arithmetic shift)
-7 >> 1 # -4 rounds toward minus infinity, exactly like -7 // 2
-1 >> 1 is still -1, because the sign bit is copied in from the left. A loop like while n: with n >>= 1 runs forever on a negative input. NeetCode's while n > 0 avoids this. If a problem hands you a negative and says "treat it as 32-bit unsigned", first do n &= 0xFFFFFFFF.
Masks: 1 << k selects bit k
A mask is a number whose bits are chosen so that an operator touches only the positions you want. The most useful mask is 1 << k: a single 1 in box k, zeros everywhere else. OR with it to turn bit k on. AND with its NOT to turn bit k off. AND with it to ask whether bit k is on. XOR with it to flip bit k.
def get_bit(n, k): # 1 or 0: is bit k on?
return (n >> k) & 1
def set_bit(n, k): # turn bit k on (OR with the mask)
return n | (1 << k)
def clear_bit(n, k): # turn bit k off (AND with the flipped mask)
return n & ~(1 << k)
def toggle_bit(n, k): # flip bit k (XOR with the mask)
return n ^ (1 << k)
get_bit(23, 3) # 0 10111: bit 3 is the 8s box, it is off
set_bit(23, 3) # 31 11111
clear_bit(23, 0) # 22 10110
toggle_bit(31, 3) # 23 back to 10111
1 << 3 turns the 8s box on and leaves every other box exactly as it was, because x | 0 = x. The same idea with AND and ~mask clears the box, because x & 1 = x everywhere except the one 0.n & 1 is the mask 1 << 0. It answers "is n odd?" in one operation, because odd numbers always have their 1s box on. 23 & 1 is 1 (odd); 22 & 1 is 0 (even).
5Four moves that solve most bit problems
The lesson gives you the operators. Interviews ask for these four moves built from them. Each one is short enough to write from memory.
Move 1: Count the set bits
Two versions. The first is the lesson's n & 1 loop: it visits every bit. The second is Kernighan's trick: n & (n - 1) clears the lowest set bit, so the loop runs once per 1 bit and skips the 0s. Both are fine; the second is what you say when the interviewer asks "can you do better?".
def countBits(n): # visit every bit: O(number of bits)
count = 0
while n > 0:
count += n & 1 # adds 1 or 0, same as the if in section 3
n >>= 1
return count
def countBitsKernighan(n): # visit only the 1 bits: O(number of 1s)
count = 0
while n:
n &= n - 1 # erase the lowest 1
count += 1
return count
countBitsKernighan(23) # 4: 10111 → 10110 → 10100 → 10000 → 00000
Why does n - 1 do that? Subtracting 1 borrows from the lowest 1: that 1 becomes 0 and every 0 to its right becomes 1. Everything to the left is untouched. AND-ing the two numbers keeps the untouched left part and zeroes the rest.
n & (n - 1) erases exactly one 1: the lowest one. Repeat until n is 0 and the number of repeats is the popcount. For 22 that is 3 turns: 10110 → 10100 → 10000 → 00000. If it takes exactly one turn, n had one 1 bit, which is Move 4.Move 2: XOR cancels pairs
Two facts from the truth table: x ^ x = 0 (a number XOR itself is nothing) and x ^ 0 = x (XOR with nothing changes nothing). XOR is also commutative, so order does not matter. XOR a whole array together and every value that appears twice cancels itself out. Whatever appears once is left over. That solves "Single Number" with no hash map and O(1) extra space.
def singleNumber(nums):
result = 0
for x in nums:
result ^= x # pairs cancel to 0, the loner survives
return result
singleNumber([4, 1, 2, 1, 2]) # 4
def missingNumber(nums): # nums holds 0..n with one missing
result = len(nums) # start with n, the index that has no slot
for i, x in enumerate(nums):
result ^= i ^ x # every present number meets its own index and cancels
return result
missingNumber([3, 0, 1]) # 2
Move 3: An integer is a set (bitmask)
If you have at most 20 or so items, one int can be the whole set: bit i on means "item i is in". Then set union is |, intersection is &, membership is (mask >> i) & 1, and the numbers 0 to 2n - 1 are all the subsets. This replaces the backtracking subsets generator with a plain for loop, and it is how DP over subsets stores its state.
def subsets_by_mask(items):
n = len(items)
out = []
for mask in range(1 << n): # 0 .. 2**n - 1, every subset once
sub = [items[i] for i in range(n) if mask >> i & 1] # bit i on → items[i] is in
out.append(sub)
return out
subsets_by_mask(["a", "b", "c"]) # 8 subsets, mask 0b101 → ['a', 'c']
A = 0b0110 # {1, 2}
B = 0b0011 # {0, 1}
A | B # 0b0111 union {0, 1, 2}
A & B # 0b0010 intersection {1}
A & ~B # 0b0100 difference {2}
(A >> 2) & 1 # 1 is 2 in A? yes
| mask | c b a (bit 2, bit 1, bit 0) | Subset of ["a", "b", "c"] |
|---|---|---|
| 0 | 000 | [] |
| 1 | 001 | ["a"] |
| 2 | 010 | ["b"] |
| 3 | 011 | ["a", "b"] |
| 4 | 100 | ["c"] |
| 5 | 101 | ["a", "c"] |
| 6 | 110 | ["b", "c"] |
| 7 | 111 | ["a", "b", "c"] |
Move 4: Power of two in one line
A power of two has exactly one 1 bit: 1, 10, 100, 1000. Move 1 said n & (n - 1) erases the lowest 1. If erasing one 1 leaves zero, there was only one. Guard against n = 0, which also gives 0 but is not a power of two, and against negatives.
def isPowerOfTwo(n):
return n > 0 and n & (n - 1) == 0
isPowerOfTwo(16) # True 10000 & 01111 = 00000
isPowerOfTwo(12) # False 01100 & 01011 = 01000
isPowerOfTwo(0) # False the n > 0 guard
6Big-O summary
Bit operations are the fastest thing a CPU does: one instruction each on a fixed-width int. The loops cost as many turns as there are bits, and a number n has about log2(n) bits, so "per bit" is O(log n), not O(n).
| Operation | Time | Extra space | Note |
|---|---|---|---|
& | ^ ~ << >> on one int | O(1)* | O(1) | * on a 32/64-bit int; Python big ints cost O(number of bits) |
countBits with n & 1 and >> | O(log n) | O(1) | one turn per bit; 32 turns for a 32-bit int |
Kernighan n &= n - 1 | O(k) | O(1) | k = number of 1 bits; never worse than the loop above |
n.bit_count() | O(1)* | O(1) | one CPU popcount instruction on fixed-width ints |
| XOR over an array (single number) | O(n) | O(1) | the hash-set version is O(n) space |
| Test / set / clear one bit with a mask | O(1) | O(1) | |
| Enumerate all subsets by mask | O(2n · n) | O(1) per mask | same count as backtracking, but no recursion |
| Power-of-two check | O(1) | O(1) | vs. O(log n) for a divide-by-2 loop |
7Mistakes to watch for
2 ^ 3 is 1 (XOR of 010 and 011), not 8. Power in Python is 2 ** 3. This is the most common bit bug in code written by people who never use bits.
1 << k + 1 is 1 << (k + 1), because + binds tighter than <<. Write (1 << k) + 1. Same for -: n & n - 1 works only because - also binds tighter than &; write n & (n - 1) anyway so the reader does not have to think.
-1 >> 1 is -1 forever, so while n: n >>= 1 never ends. Use while n > 0 as the lesson does, or mask to 32 bits first with n &= 0xFFFFFFFF.
~1 is -2, not 0. Python has no fixed width, so NOT flips infinitely many bits and the result is -n - 1. To "flip the low 8 bits only" write n ^ 0xFF (or ~n & 0xFF).
Bit 0 is the rightmost box, worth 1. "The 3rd bit" is ambiguous; say "bit 3" and mean 1 << 3 = 8. In format(n, "08b") the string index 0 is bit 7, the opposite direction.
singleNumber works only if every other value appears an even number of times. With three copies (LeetCode 137) or two singles (LeetCode 260) the plain XOR gives garbage. Check the problem statement before you reach for it.
0 & -1 == 0, so n & (n - 1) == 0 says 0 is a power of two. It is not. Always n > 0 and ....
Reverse Bits and Sum of Two Integers assume a 32-bit int. In Python a carry can climb past bit 31 forever. Mask with 0xFFFFFFFF after every step, and convert back to a signed value at the end if the top bit is set.
bin(n).count("1") is fine in production. In an interview, write the loop first, then say "in Python I would use bit_count()". The loop is what they are testing.
Interview one-liners
- "Each bit position is a power of two, so
n >> 1isn // 2andn << 1isn * 2." - "
n & 1reads the lowest bit; it tells me odd or even in O(1)." - "
n & (n - 1)clears the lowest set bit, so I can count 1s in O(number of 1s) or test for a power of two in one line." - "XOR is its own inverse:
x ^ x = 0andx ^ 0 = x, so XOR-ing an array cancels every pair and leaves the single number, O(n) time, O(1) space." - "
1 << kis a mask for bit k: OR sets it, AND with the NOT clears it, AND tests it." - "An int can be a set of up to 63 items;
range(1 << n)enumerates every subset." - "Python ints never overflow, so for 32-bit problems I mask with
0xFFFFFFFF; and~nis-n - 1."
8Practice, in this order
Each of these is a five-line function once you see the trick. The point is to see the trick fast. Do them with no notes open, then compare with the moves above.
| # | Problem | Move it trains | Level |
|---|---|---|---|
| 191 | Number of 1 Bits | n & 1 loop, then Kernighan | Easy |
| 231 | Power of Two | n & (n - 1) == 0 with the n > 0 guard | Easy |
| 136 | Single Number | XOR cancels pairs | Easy |
| 268 | Missing Number | XOR values against indexes | Easy |
| 461 | Hamming Distance | XOR then count bits | Easy |
| 338 | Counting Bits | dp[i] = dp[i >> 1] + (i & 1), DP meets bits | Easy |
| 190 | Reverse Bits | read bit i with a mask, write bit 31 - i, 32 turns | Easy |
| 78 | Subsets | bitmask enumeration instead of backtracking | Medium |
| 371 | Sum of Two Integers | XOR is add-without-carry, AND << 1 is the carry, 32-bit mask | Medium |
| 137 | Single Number II | count each bit position mod 3 (XOR alone is not enough) | Medium |
Your next step today: LeetCode 191. Write the n & 1 loop from memory, run it on 23 and check you get 4, then rewrite it with n &= n - 1 and check again.
9Self-check
Answer out loud before opening. If you cannot answer in one breath, reread that section.
Q1What is 10111 in decimal, and how did you get it?
23. Read the place values of the 1s from the right: 1 + 2 + 4 + 16 = 23. The 8s box is 0, so skip it.
Q2Give the one row of each truth table that makes it different from the others.
AND: only 1 & 1 gives 1. OR: only 0 | 0 gives 0. XOR: 1 ^ 1 gives 0 (same inputs cancel). NOT has one input and flips it.
Q3What does n >> 1 compute, and what happens to the lowest bit?
n // 2. Every bit slides one box to the right, so every place value halves. The bit in the 1s box falls off; it was the remainder of the division.
Q4Shift 001 left three times in a 3-bit register. What are the three results and why is the last one surprising?
010, 100, 000. The 1 falls off the left edge in the last step, so 4 doubled becomes 0 instead of 8. Python ints would give 8 because they have no fixed width.
Q5Why does n & 1 tell you whether the lowest bit is on?
1 is ...0001: only bit 0 is set. AND gives 1 only where both inputs are 1, so every other position becomes 0 and bit 0 keeps its value. The result is 1 if bit 0 was 1, else 0. Same test tells you odd or even.
Q6Trace it: run countBits(10). Give n, n & 1 and count after each turn.
10 is 1010. Turn 1: n=1010, n&1=0, count=0, shift → 101. Turn 2: n=101, n&1=1, count=1, shift → 10. Turn 3: n=10, n&1=0, count=1, shift → 1. Turn 4: n=1, n&1=1, count=2, shift → 0. Loop ends. Answer 2.
Q7What does n & (n - 1) do, and why?
It clears the lowest set bit. Subtracting 1 turns the lowest 1 into 0 and all 0s to its right into 1s, leaving higher bits alone. AND keeps the untouched higher bits and zeroes the flipped tail. 22 (10110) becomes 20 (10100).
Q8Trace it: count the 1s in 12 using Kernighan's loop. Show n after each n &= n - 1.
12 is 1100. Turn 1: 1100 & 1011 = 1000 (8), count 1. Turn 2: 1000 & 0111 = 0000, count 2. n is 0, stop. Answer 2, in 2 turns instead of 4.
Q9Why does XOR-ing [4, 1, 2, 1, 2] give 4?
x ^ x = 0 and x ^ 0 = x, and order does not matter. So the two 1s cancel, the two 2s cancel, and 4 ^ 0 ^ 0 = 4. Running values: 4, 5, 7, 6, 4.
Q10Write the expressions to set, clear and test bit k of n.
Set: n | (1 << k). Clear: n & ~(1 << k). Test: (n >> k) & 1 (or n & (1 << k) != 0). Toggle: n ^ (1 << k).
Q11Why is ~1 equal to -2 in Python and not 0?
Python ints have no fixed width. NOT flips every bit including infinitely many leading zeros, which in two's complement is the number -n - 1. To flip only the low 8 bits use n ^ 0xFF.
Q12How do you check "is n a power of two" in O(1), and which input needs a guard?
n > 0 and n & (n - 1) == 0. A power of two has exactly one 1 bit, so clearing the lowest 1 leaves 0. The guard is for n = 0, where 0 & -1 is also 0 but 0 is not a power of two.