Core Techniques
Bit Basics covers the model - what a bit is, what the operators do. This page is the toolbox built on top of it: setting, clearing, toggling, rotating, and the handful of identities that carry most single-integer interview problems. This is the page to actually memorize.
Single-bit ops
Get a bit
Every single-bit operation is the same two-step move: build the mask 1 << i, then combine it. Which operator you combine with decides what happens. Getting reads a column without writing it; setting and clearing write one, testing and toggling test and flip one.
x >> i slides bit i down to position 0, pushing everything below it off the end. & 1 then throws away everything still sitting above position 0, leaving just that one bit as a plain 0 or 1.
x0001011022i down to position 0Everything below bit i falls off the end.x >> i000000102(x >> i) & 100Set a bit
1 << i is a mask with a single 1 in column i and 0 everywhere else. OR-ing it into x can only turn that column on - every other column ORs against 0 and passes through unchanged, and a column that was already 1 stays 1.
x0001011022i, 0 everywhere else.1 << i000010008iOnly ever turns a column on.x | (1 << i)0001111030Clear a bit
~(1 << i) flips the mask: every column is 1 except column i, which is 0. AND-ing that into x forces column i to 0 (anything AND 0 is 0) while every other column ANDs against 1 and passes through unchanged.
x0001011022i, 0 everywhere else.1 << i000010008i.~(1 << i)11110111247iOnly ever turns a column off.x & ~(1 << i)0001011022Test a bit
Same two-step move, the other two operators: AND without a shift asks whether a column is on, XOR flips it. Testing looks like getting, but stops one step earlier: x & (1 << i) keeps column i and zeroes the rest, without sliding anything down. The survivor is therefore the mask's value, not 1 - testing bit 3 yields 8, testing bit 7 yields 128. That is why the comparison is != 0 and never == 1. Reach for get_bit when you want the digit; reach for this when you only want the yes/no.
x0001011022i, 0 everywhere else.1 << i000010008iSurvives with its place value - 8 for bit 3, 128 for bit 7.x & (1 << i)000000000x & (1 << i) != 0FalseToggle a bit
XOR is the operator that asks "do these differ?" - so XOR-ing against the mask flips column i (it differs from 1 exactly when it was 0) and leaves every other column alone (anything XOR 0 is itself). Unlike set and clear, toggle is not idempotent: apply it twice and you are back where you started, which is what makes XOR its own inverse.
x0001011022i, 0 everywhere else.1 << i000010008iFlips that one column, leaves the rest alone.x ^ (1 << i)0001111030x - toggle is its own undo, unlike set and clear.x ^ (1 << i) ^ (1 << i)0001011022Odd or even
Bit 0 is 1 for every odd number and 0 for every even one, so parity is just get_bit with i = 0 fixed - no % 2, no shift needed since the bit to test is already at position 0. It earns its own name because it is the one bit test you will write without thinking about masks at all.
No mask to build and no i to choose - the only bit that matters is already at
position 0. Scrub the register and watch the answer flip on every step:
n0001011022n & 100n is oddThe same bit, as a yes/no.n & 1 == 1False% version, for comparisonSame answer for non-negative n; & 1 is just the shorter read.n % 200Borrow & carry
Clear all set bits
Clearing the lowest set bit needs a trick (clearing the lowest set bit) because that bit's position is not known ahead of time. Clearing every set bit at once needs no trick at all: there is nothing left to locate. A value never disagrees with itself, so XOR-ing the register against itself cancels every column to 0 - equivalently, AND it against an all-zero mask.
x10101100172x ^ x000000000x & 0000000000Clear the lowest set bit
Subtracting one turns the lowest 1 into 0 and every zero below it into 1. ANDing back against x keeps only the bits above, so exactly one set bit disappears per application. Loop until zero and the iteration count is the popcount: that is Brian Kernighan's algorithm, and it runs in number of set bits steps rather than width steps.
Its most famous corollary: a power of two has exactly one set bit, so n > 0 and n & (n - 1) == 0.
x10101100172x - 110101011171x & (x - 1)10101000168x is a power of twoOne set bit left to clear, and clearing it empties the word.x > 0 and x & (x - 1) == 0Falsewhile x: x &= x - 1; c += 14Count set bits
Counting the 1s in x - its popcount, or Hamming weight - is built into Python: x.bit_count() (3.10+) does it directly, no loop required. The Kernighan loop from clearing the lowest set bit is the manual version of the same idea: strip the lowest set bit one at a time and count the strips, which costs number of set bits iterations instead of width iterations. Reach for the loop only on Pythons without bit_count(), or when you need the count as a side effect of the stripping itself.
x10101100172x.bit_count()4while x: x &= x - 1; c += 14Adding 1: the mirror carry
x - 1 is a borrow that runs right-to-left: it flips every trailing 0 to 1 until it hits the lowest 1, which it flips to 0 and stops there (clearing the lowest set bit). Adding 1 runs the exact mirror-image carry instead: it flips every trailing 1 to 0 until it hits the lowest 0, which it flips to 1 and stops there. So x + 1 sets the lowest clear bit and clears every bit below it - the opposite of x & (x - 1) clearing the lowest set bit and setting every bit below it.
x10101100172x + 110101101173Set the lowest clear bit
Every number short of "all 1s" has a lowest clear bit somewhere in its width. x + 1's carry chain flips every trailing 1 to 0 and stops at that lowest 0, setting it to 1 (the mirror carry walks through why) - but that wipes out the trailing ones along the way, which is more than "just set one bit." ORing the carry result back against the original x restores those trailing ones, since x still has them as 1; the lowest clear bit survives the OR too, because it went from 0 in x to 1 in x + 1, and OR only ever turns bits on. Net effect: exactly one bit changes - the lowest clear bit flips on, everything else, trailing ones included, stays exactly as it was.
x10110111183x + 110111000184x | (x + 1)10111111191Set all trailing zeros
x - 1 sets every bit below the lowest set bit (clearing the lowest set bit) without touching the lowest set bit itself, which flips to 0. OR-ing that back against x restores the lowest set bit while keeping the newly-set bits below it, so the net effect is "leave everything above the lowest set bit alone, and turn on everything from the lowest set bit down to bit 0."
x10101100172x - 110101011171x | (x - 1)10101111175Clear all trailing ones
x + 1 sets every bit below the lowest clear bit (the mirror carry) and flips the lowest clear bit itself to 1. ANDing that back against x restores every bit above the lowest clear bit while wiping out the bits below it, since those were the trailing 1s that x + 1 just turned to 0. Net effect: "leave everything above the lowest clear bit alone, and turn off everything from the lowest clear bit down to bit 0" - the exact mirror of setting all trailing zeros's x | (x - 1).
x10101111175x + 110110000176x & (x + 1)10100000160Isolate the lowest set bit
Because -x == ~x + 1, the carry chain stops at the lowest set bit, leaving every higher column inverted (so it cannot match) and every lower column zero. Only the lowest set bit agrees. Use it to grab the next element out of a bitmask, and (x & -x).bit_length() - 1 to get its index.
x10101100172xShown inside the 8-bit word.-x == ~x + 10101010084x & -x000001004x is 0, which is why this reads -1 there.(x & -x).bit_length() - 100102x ^ (x - 1)000001117Structural & arithmetic tricks
Powers of two, four, eight, sixteen
Being a power of two is "exactly one set bit" (clearing the lowest set bit): n > 0 and n & (n - 1) == 0. Being a power of a bigger power of two - 4, 8, 16, ... - is the same one-bit test plus a constraint on which bit is set: 4's set bits sit at indices 0, 2, 4, ...; 8's at 0, 3, 6, ...; 16's at 0, 4, 8, .... Rather than compute that index with bit_length(), a constant mask with a 1 at exactly those positions answers "is the lone bit one of them?" with a single AND: 0b01010101...0101 (every 2nd bit) for four, 0b01001001...1001 (every 3rd bit) for eight, 0b00010001...0001 (every 4th bit) for sixteen - written as binary literals, the period is visible in the digits themselves.
n000000000100000064n - 1000000000011111163n is a power of twoZero exactly when n has one set bit.n & (n - 1)000000000000000000b0101...0101010101010101010121845n is a power of fourNon-zero only for 1, 4, 16, 64, ...n & mask_40000000001000000640b1001...1001100100100100100137449n is a power of eightNon-zero only for 1, 8, 64, 512, ...n & mask_80000000001000000640b0001...000100010001000100014369n is a power of sixteenNon-zero only for 1, 16, 256, 4096, ...n & mask_1600000000000000000n = 64 is a good register to scrub through: it is 2**6, so its lone set bit sits at index 6 - that's why it passes power-of-two, power-of-four, and power-of-eight (6 is a multiple of 2 and 3) but fails power-of-sixteen (6 is not a multiple of 4).
Reversing all the bits of a fixed-width integer
To reverse all 32 bits of an integer, repeatedly split it into blocks and swap them: first the two 16-bit halves, then 8-bit blocks within each half, then 4-bit, 2-bit, and finally 1-bit blocks. Each step uses a mask to isolate the relevant bits and shifts them into their mirrored position, then ORs the two halves together. After five passes every bit has migrated to its mirror position - a fixed five operations instead of a 32-iteration bit-by-bit loop.
Each later pass halves the block size (8, then 4, then 2, then 1 bit) and does the identical swap-and-shift within each half from the previous pass - by the last pass, every individual bit has traded places with its mirror.
The same idea on a single byte needs only three passes - swap 4-bit halves, then 2-bit pairs within each half, then adjacent single bits - since log2(8) = 3. Scrub the register below to see every pass update together:
n10110100180(n & 0b11110000) >> 4 | (n & 0b00001111) << 40100101175(n & 0b11001100) >> 2 | (n & 0b00110011) << 20001111030(n & 0b10101010) >> 1 | (n & 0b01010101) << 10010110145Swapping two variables without a temp
XOR has two useful properties: a value XORed with itself is 0, and a value XORed with 0 is unchanged. Chaining three XOR assignments swaps a and b in place, with no temporary variable:
a.a1015b.b0113aHolds combined info of both.a = a ^ b1106a into bOriginal a, cancelled out of the combined value.b = b ^ a1015b into aOriginal b, cancelled out - swap complete, no temp used.a = a ^ b0113a and b are the same variablearr[i], arr[i] = arr[i] ^ arr[i], ... zeroes the slot on the first line, because a value XORed with itself is always 0 - there is nothing left to swap with. This only bites when the two "variables" can alias the same storage (the same array index passed twice); plain distinct local variables are never a problem.
Add without +
XOR gives the sum of two bits ignoring any carry (0^1=1, but 1^1=0 where a real carry would appear); AND finds exactly the columns where both bits were 1 - the columns a carry gets generated from. Shifting that left by one moves each carry into the column it affects, exactly like carrying a digit in long addition. Assigning both at once (a, b = a ^ b, (a & b) << 1) and repeating drives b toward 0, because each pass pushes any remaining carry one column further left, and a fixed-width number runs out of columns. When b finally hits 0, a holds the sum.
a.a0000110012b.b0000101010a ^ b000001106(a & b) << 10001000016(a^b) ^ ((a&b)<<1)0001011022((a^b) & ((a&b)<<1)) << 1000000000Rotate left by k
A shift throws bits away; a rotation wraps them around instead - the bits that fall off one end reappear at the other. Rotation only makes sense once you fix a width w (there is no "rotate" on Python's unbounded integers without deciding how many bits you are rotating within).
(n << k) slides every bit left by k, which is exactly what pushes the top k bits off the word. (n >> (w - k)) recovers those same top k bits at the bottom of the word instead (a plain right shift by the complementary distance). OR-ing the two together drops the wrapped bits back in where the shift emptied them out, then the final & ((1 << w) - 1) trims the result back down to w bits.
That one line is four separate moves stacked together. Toggle a bit or scrub
k below and watch all four terms move together - the two middle rows are
drawn 16 bits wide because they genuinely overflow the word; the trim is what
pulls them back in.
n10110100180kPast bit 7 is outside the word.n << k00000101101000001440k, re-enteringA plain right shift by the complementary distance.n >> (w - k)00000000000001015(n << k) | (n >> (w - k))00000101101001011445... & ((1 << w) - 1)10100101165Scrub k to 0 or 8 and the result is n again: a rotation by the full
width is the identity. Both ends still work here only because Python shifts by
any distance - in C, n >> (w - k) at k = 0 shifts by the full width, which
is undefined behaviour, so fixed-width code guards with k %= w first.
Rotate right by k
Mirror image of rotate-left: (n >> k) slides every bit right by k, dropping the bottom k bits off the word. (n << (w - k)) recovers those same bottom k bits at the top of the word instead. OR-ing the two together and masking to w bits puts them back where the shift emptied them out.
The same walkthrough, mirrored: the two shifts swapped, so the bottom k bits
are the ones that leave and re-enter at the top.
n10110100180kBit 0 is outside the word.n >> k000000000001011022k, re-enteringA plain left shift by the complementary distance.n << (w - k)00010110100000005760(n >> k) | (n << (w - k))00010110100101105782... & ((1 << w) - 1)10010110150Hardware and most systems languages expose this as a single instruction rather than a mask-and-shift idiom - x86 has ROL/ROR, and C compilers usually recognize the mask-and-shift pattern above and emit one anyway. Python's own int has no built-in rotate because it has no fixed width to rotate within; you always supply w yourself.
Bitmasks as sets
Bitmasks: a whole set inside one integer
If you have at most about 20 to 30 items, you do not need a set. Assign each item a bit position and the entire subset becomes a single integer, with set operations that are one machine instruction each.
The register below is the set m over n = 8 items, with i = 2 and a fixed
partner set b = 0b00001111. Toggle a bit and every membership answer moves
with it.
nThe two ends of the range every bitmask loop walks.0 / (1 << n) - 111111111255iIdempotent - adding twice is adding once.m |= 1 << i0000111014iThe inverted mask spares every other column.m &= ~(1 << i)0000101010iThe only one of the three that is not idempotent.m ^= 1 << i0000111014im >> i & 1FalsebShown here as the union.a | b / a & b0000111115m - ba & ~b000000000ba ^ b000001015m is a subset of ba & b == aTruem.bit_count()00102n itemsm ^ ((1 << n) - 1)11110101245Two enumeration patterns are worth memorizing verbatim:
Small fixed universe (letters of the alphabet, up to ~20 cities, a set of visited states), and you need set operations to be free. Count the Number of Consistent Strings maps letters to bits; Palindrome Permutation uses one integer as 26 parity counters. If the universe is large or unbounded, use a real set.
Reference
The cheatsheet
Everything above, compressed. This is the page to reread before an interview.
n oddFaster to read than n % 2.n & 1i setn >> i & 1n a power of twoExactly one set bit.n > 0 and n & (n - 1) == 0n all ones (2**k - 1)n & (n + 1) == 0x and y differ in signThe sign bit is the only one that matters.(x ^ y) < 0n & (n >> 1) == 0n & -n(n & -n).bit_length() - 1floor(log2(n)) for n > 0.n.bit_length() - 1n ^ (n - 1)k bitsn & ((1 << k) - 1)n & (n - 1)n & (n + 1)n | (n + 1)n | (n - 1)n’s widthNot ~n - Python’s ~ sign-extends forever. bit_length() is 0 for n == 0, so grow the mask with a loop there instead.n ^ ((1 << n.bit_length()) - 1)bin(n).count("1").n.bit_count()while n: n &= n - 1; c += 1(x ^ y).bit_count()n0 for n == 0.n.bit_length()2**kRight shift floors.n << k / n >> knFor n > 1.1 << (n - 1).bit_length()n > 0.1 << (n.bit_length() - 1)-nTwo’s complement, by definition.~n + 1~nThe Python identity people forget.-n - 1+XOR is sum-without-carry, (a & b) << 1 is the carry; repeat until no carry remains.while b: a, b = a ^ b, (a & b) << 1n >> 31 is all-1s for negative n, all-0s otherwise - it is a sign-selected mask, not a comparison.(n ^ (n >> 31)) - (n >> 31)a < b with a sign-bit trick too; shown here for the shape - a plain min(a, b) is clearer in Python.b ^ ((a ^ b) & -(a < b))a and b are the same variable.a ^= b; b ^= a; a ^= breduce(xor, nums)nConsecutive codes differ by one bit.n ^ (n >> 1)enc = a ^ k, a = enc ^ ki to vClear first, then drop the new value in.x & ~(1 << i) | (v << i)[lo, hi) to vSame clear-then-drop-in idea as writing one bit, just with a wider mask.x & ~(((1 << (hi - lo)) - 1) << lo) | (v << lo)k bitsSame as x % 2**k for non-negative x.x & ((1 << k) - 1)[lo, hi)Generalizes "take the low k bits" to any window - shift the window to the origin first, then mask its width.(x >> lo) & ((1 << (hi - lo)) - 1)Where to go next
- Counting Bits - popcount, Hamming distance, set-bit count as a key.
- Bit Patterns & Properties - powers of two, runs and gaps, folding across a range.
- Rebuilding a Number - complement, reverse, encode/decode, arithmetic without
+. - XOR - cancellation, single number, folding and inverting a sequence.
- Bitmask as a Set - one integer models a set of small values.