Bit Manipulation
Treat an integer as a row of on/off switches instead of a quantity. A handful of operators — XOR, AND, shifts — turn "find the number that appears once" and "count the set bits" into one clean pass with no extra memory.
A number is a row of switches
Everything in this pattern comes from one shift in perspective: stop reading
12 as a quantity and start reading it as 1100 — four columns,
each either on or off. Bitwise operators work on every column at once,
independently, in a single CPU instruction. That is why these solutions come out
O(1) extra space with no hash set in sight.
| op | reads as | column rule | you reach for it when… |
|---|---|---|---|
a & b | AND | 1 only if both are 1 | testing or clearing bits, masking |
a | b | OR | 1 if either is 1 | turning bits on, merging masks |
a ^ b | XOR | 1 if they differ | toggling, cancelling pairs, "find the odd one" |
~a | NOT | flip every column | building the complement of a mask |
a << k | shift left | slide up k columns | multiply by 2k, build 1 << i masks |
a >> k | shift right | slide down k columns | divide by 2k, walk the bits |
XORThe four facts worth memorising
x ^ x == 0— anything cancels itself. This is the entire Single Number family.x ^ 0 == x— zero is the identity, so it is always the safe starting accumulator.x ^ y ^ y == x— XOR is its own inverse; applying it twice undoes it.- XOR is commutative and associative, so the order of a whole array of XORs never matters.
Put those together and "one number appears once, the rest appear twice" stops being a counting problem. Fold the array into a single accumulator and the duplicates delete each other on the way past.
Five moves that cover almost every problem
M1n & (n - 1) — clear the lowest set bit
Subtracting 1 borrows through the trailing zeros: the lowest 1 turns into a 0 and every 0 below it turns into a 1, while everything above is left alone. ANDing the two therefore keeps the untouched top and wipes the tail — exactly one set bit disappears.
Two consequences you will use constantly: looping n = n & (n - 1) until
n is zero counts the set bits in O(number of 1s) instead of
O(32), and a number with exactly one set bit satisfies
n & (n - 1) == 0 — the power-of-two test.
M2n & -n — isolate the lowest set bit
The mirror image of M1: instead of deleting the lowest 1, it throws away everything
else. In two's complement -n is ~n + 1, which happens
to match n from the lowest set bit downward and disagree everywhere above it.
12 & -12 == 4. This is the standard way to pick a "splitting bit" in
LC 260, and it is how a Fenwick tree walks its indices.
M3Single-bit masks with 1 << i
1 << i is a number with a single 1 in column i. Combine it
with the operators above and you get the four things you ever want to do to one bit:
bit = 1 << i # a mask with only column i turned on
is_set = (n >> i) & 1 # TEST -> 0 or 1 (shift down, look at the last bit)
n = n | bit # SET -> force column i on
n = n & ~bit # CLEAR -> force column i off
n = n ^ bit # TOGGLE -> flip whatever is there
M4Masking — keeping only the part you care about
A mask is just a number whose 1s mark the columns you want to keep.
n & 0xFF keeps the low 8 bits and throws the rest away;
n & 1 is the "is it odd?" test; (1 << k) - 1 builds a
block of k ones, which is the cheapest way to make a custom-width mask.
M5Shifts as cheap arithmetic
n << 1 doubles, n >> 1 halves (rounding down,
always — -3 >> 1 == -2, not -1). Walking a number bit by bit
with n >>= 1 is the standard fixed-width loop for problems like Reverse Bits.
Two's complement, and the gotcha that breaks LC 371
Hardware stores a negative number as its two's complement: flip every bit,
then add 1. In 8 bits, 5 is 00000101, so -5 is
11111011. The top bit doubles as the sign, and the beauty of the scheme is that
ordinary binary addition then "just works" for negatives with no special cases.
Python agrees with all of that in spirit — ~x == -x - 1, and
(-5 >> i) & 1 gives you the right two's-complement bit — but with one
enormous difference:
That is the whole difficulty of LC 371 (Sum of Two Integers). The
carry-propagation loop below is correct C; in Python, a negative operand keeps generating a
carry that marches up forever and the while loop never ends. The fix is to
& 0xFFFFFFFF after every step, which simulates a 32-bit register, and then
translate the result back into a signed Python int at the very end.
MASK = 0xFFFFFFFF # 32 ones: "keep only the low 32 columns"
INT_MAX = 0x7FFFFFFF # largest positive value a signed 32-bit int can hold
def get_sum(a, b):
# Step into 32-bit land: from here on every value is an unsigned
# 32-bit bit pattern, not a Python integer.
a = a & MASK
b = b & MASK
while b != 0:
# Columns where both have a 1 are the ones that carry; the shift
# moves each carry into the column it is worth.
carry = ((a & b) << 1) & MASK
# XOR adds the columns that do NOT carry.
a = (a ^ b) & MASK
b = carry
if a <= INT_MAX:
return a
# The top bit is set, so this pattern means a negative number.
# Undo the two's complement to get Python's own negative int back.
return ~(a ^ MASK)
The other place this bites: printing. bin(-5) gives
'-0b101' — a minus sign glued to the magnitude, not the two's-complement
pattern you were expecting. When you want to see the real 32-bit bits, print
bin(x & 0xFFFFFFFF) instead.
When to reach for bits
- The statement says "without using extra memory" or "in O(1) space" — usually a XOR-cancellation problem.
- The words binary, bit, power of two, complement, mask, parity appear literally.
- Elements appear a fixed number of times and one is the odd one out (LC 136 / 137 / 260).
- You need every subset of a set with
n ≤ 20— mask0 .. 2n-1enumerates them, and each mask doubles as a DP state (bitmask DP). - You are tracking a small set of flags (26 lowercase letters, visited cities) and a hash set feels heavy — pack them into one integer.
- Two things need comparing column by column: Hamming distance, Gray code, common prefixes.
x ^ x ^ x == x and the trick quietly fails (that is why LC 137 is a different
algorithm). And bitmask enumeration is 2n — fine at n = 20, hopeless at n = 40.
The four templates to memorize
T1XOR fold — cancel the pairs
def single_number(nums):
running_xor = 0 # x ^ 0 == x, so 0 is the safe start
for num in nums:
# Every value that appears twice cancels itself out (x ^ x == 0),
# so after the whole pass only the loner is left standing.
running_xor = running_xor ^ num
return running_xor
The same fold solves LC 268 (Missing Number) — XOR all the indices and all the values together, and everything pairs off except the number that was never there:
def missing_number(nums):
# Indices run 0..n-1 but the values are drawn from 0..n, so seed the
# accumulator with n — the one index that has no slot of its own.
missing = len(nums)
for index, num in enumerate(nums):
missing = missing ^ index ^ num
return missing
T2Brian Kernighan — one loop per set bit
def count_set_bits(n):
count = 0
while n != 0:
# Erase the lowest 1. The loop therefore runs exactly
# once per set bit, not once per column.
n = n & (n - 1)
count += 1
return count
def is_power_of_two(n):
# A power of two has exactly one set bit, so a single erase empties it.
# The n > 0 guard matters: 0 & -1 == 0 would sneak through otherwise.
return n > 0 and (n & (n - 1)) == 0
In a real interview bin(n).count("1") or n.bit_count() (Python 3.10+)
is the honest one-liner — write the loop only when asked to show the mechanism.
T3Fixed-width bit walk
def reverse_bits(n):
result = 0
# Exactly 32 iterations — the width is part of the problem statement,
# and Python will not stop the loop for us the way a register would.
for _ in range(32):
lowest_bit = n & 1 # read the bit about to fall off n
result = (result << 1) | lowest_bit # push it onto result
n = n >> 1
return result
The shape — pull the low bit off one number, push it onto another — is the same one used for binary conversion, parity and Gray code.
T4Bitmask enumeration — every subset
def subsets(nums):
n = len(nums)
all_subsets = []
# There are 2**n subsets and 2**n masks of n bits. Line them up:
# mask 0b101 with nums = [1, 2, 3] means "take nums[0] and nums[2]".
for mask in range(1 << n):
current = []
for index in range(n):
if mask & (1 << index): # is column "index" on in this mask?
current.append(nums[index])
all_subsets.append(current)
return all_subsets
Same idea powers bitmask DP: the mask becomes the state ("which cities have I visited"),
and dp[mask] is indexed by an integer instead of a frozenset.
Three problems that bend the basic tricks
LC 338Counting Bits — DP on the shift
Counting each number from scratch is O(n log n). Instead notice that value and
value >> 1 have the same bits except for the one that got shifted off —
and we already computed the answer for the smaller number.
def count_bits(n):
bits = [0] * (n + 1)
for value in range(1, n + 1):
half = value >> 1 # value with its last bit chopped off
last_bit = value & 1 # the bit we chopped: 0 or 1
bits[value] = bits[half] + last_bit
return bits
LC 137Single Number II — count columns mod 3
Every value appears three times except one. XOR is useless here, so drop to the column level: if a bit is set in a tripled value it contributes 3 to that column's tally, so any column whose tally is not a multiple of 3 belongs to the loner. There is a famous two-variable "ones / twos" version — this one is the one you can explain.
def single_number_ii(nums):
result = 0
for bit_position in range(32):
ones_in_column = 0
for num in nums:
# For negatives Python still reports the correct
# two's-complement bit here, which is what we want.
if (num >> bit_position) & 1:
ones_in_column += 1
# The tripled values contribute 0 or 3; the leftover is the loner's bit.
if ones_in_column % 3 != 0:
result = result | (1 << bit_position)
# Bit 31 set means the 32-bit pattern is negative — Python would
# otherwise hand back a large positive number.
if result >= (1 << 31):
result -= (1 << 32)
return result
LC 260Single Number III — split into two buckets
Two loners, everything else paired. XORing everything gives first ^ second —
not an answer, but every set bit in it is a column where the two loners differ.
Pick any one of those columns and it partitions the array into two halves, each containing
exactly one loner. Then T1 twice.
def single_number_iii(nums):
xor_of_both = 0
for num in nums:
xor_of_both = xor_of_both ^ num # pairs vanish; first ^ second remains
# Isolate one column where the two loners disagree (M2).
splitting_bit = xor_of_both & -xor_of_both
first = 0
second = 0
for num in nums:
# Duplicates always land in the same bucket as each other,
# so they still cancel inside their own bucket.
if num & splitting_bit:
first = first ^ num
else:
second = second ^ num
return [first, second]
Where bit code goes wrong
& 0xFFFFFFFF,
and convert back to a signed value at the end.
&, | and ^ bind looser than
==, + and - in Python — the opposite of what most people
assume. Parenthesise every bitwise expression.
# WRONG: parses as n & (1 + 1) -> n & 2
if n & 1 + 1:
...
# WRONG: parses as x & (mask == 0). For any non-zero mask that is
# x & False, i.e. 0 -- so the branch never runs.
if x & mask == 0:
...
# RIGHT: say exactly what you mean
if (x & mask) == 0:
...
while n: with a negative n-8 >> 1 is -4, then -2, then -1,
then -1 forever. while n: n >>= 1 is an infinite loop.
Mask first (n &= 0xFFFFFFFF) or use a fixed for _ in range(32) loop.
n & (n - 1) == 0 is true for n = 0 as well (and for negatives, and
it has the precedence bug above). The correct test is
n > 0 and (n & (n - 1)) == 0.
x ^ x ^ x == x, so with triples the loner is not what survives —
LC 137 needs the mod-3 column tally instead. Check the multiplicity in the statement before
reaching for the fold.
bin() on negativesbin(-5) returns '-0b101', not a two's-complement pattern, and
len(bin(x)) - 2 silently gives the wrong width for negatives. Debug with
format(x & 0xFFFFFFFF, "032b") so you are looking at the bits the algorithm
actually manipulates.
Practice ladder
In this order — easiest first. Tick each one as you solve it, and re-solve anything that took more than 25 minutes two days later.