DSA · Bit Manipulation
One integer is thirty-two switches
A number is already a row of bits, and one mask lets you read or change any single position without disturbing the rest. You will convert 13 by hand, check, set and clear its bit 2, then count set bits the way Brian Kernighan does — one round per 1, not one per position.
Flip bits on an 8-cell row and watch the decimal move →01 The idea
The number is already a row of switches
Your computer does not store 13 as a 1 followed by a 3. It stores a fixed row of switches — 32 of them in an int — and 13 is the pattern 00001101 in the last eight of them. Counting from the right-hand end, the positions are worth 1, 2, 4, 8, 16 and so on, and the switches that are on add up to the number: 8 + 4 + 1 = 13.
Once you can see that row, a whole family of questions stops needing a loop. Is position 2 on? Turn position 2 off. How many are on? The six operators that do this work on every position at once. AND, OR and XOR line two numbers up and compare them bit by bit, NOT flips every bit of one number, and the two shift operators slide the whole row left or right. The only thing you have to supply is a mask — a number built so that the positions you care about hold 1 and every other position holds 0.
02 Worked example
Check, set and clear bit 2 of 13
One number, n = 13, and one position, bit 2. Each row below is the same eight cells; the labels on the left say which number the row is showing. Read the cells right to left, because bit 0 sits at the right-hand end. Only the sixth cell from the left — position 2 — is ever in play.
One mask did all three jobs, and the operator you paired it with decided what happened. AND read the position, OR forced it on, AND with the complement forced it off. Nothing else in the number moved: 13 and 9 differ in exactly one bit, and the decimal changed by exactly 4 — the place value of bit 2. That is the invariant the four one-liners below are built to protect.
03 The code
Four one-liners, one mask
Start with the tables the one-liners rest on, then read the four masks side by side. Only one of the four changes shape, and that is clear, which needs the complement.
Truth tables, one bit at a time
These operators do not treat the number as a whole. They line the two numbers up and apply the table below to each of the 32 positions independently.
| a | b | a & b (AND) | a | b (OR) | a ^ b (XOR) | ~a (NOT) |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 | 1 | 1 |
| 1 | 0 | 0 | 1 | 1 | 0 |
| 1 | 1 | 1 | 1 | 0 | 0 |
Read them as sentences. AND gives 1 only when both inputs are 1, so it keeps a bit and drops everything else. OR gives 1 when at least one is 1, so it can only add 1s. XOR gives 1 only when the two inputs differ, which is why 1 ^ 1 is 0 and why XOR is the only one of the three that can remove a 1. NOT takes one input and flips every position of it.
Shifting slides the whole row
| Expression | Binary | Decimal | What happened |
|---|---|---|---|
| 13 << 1 | 00011010 | 26 | Every bit moved one place left, a 0 slid in at the right. That is ×2. |
| 13 << 2 | 00110100 | 52 | Two places left. That is ×4. |
| 13 >> 1 | 00000110 | 6 | One place right; the 1 that was in bit 0 falls off the end. 13 ÷ 2 rounded down. |
| 13 >> 2 | 00000011 | 3 | Two places right, two bits lost. 13 ÷ 4 rounded down. |
| 1 << 2 | 00000100 | 4 | A lone 1 walked up to position 2. This is the mask. |
The four one-liners, with mask = 1 << i
| Goal | One-liner | Mask in binary, i = 2 | On n = 13 |
|---|---|---|---|
| Read bit i | (n & mask) != 0 | 00000100 | 13 & 4 = 4, so the bit is 1 |
| Switch bit i on | n | mask | 00000100 | 13 | 4 = 13, no change |
| Switch bit i off | n & ~mask | 11111011 | 13 & 251 = 9 |
| Flip bit i | n ^ mask | 00000100 | 13 ^ 4 = 9 |
Why clear is the odd one out. The other three want to act on position i, and a mask with a single 1 there is enough. Clear wants to preserve everything except position i, so its mask has to be 1 everywhere else — that is exactly ~mask, 11111011. AND then copies every other bit through untouched and forces bit 2 to 0. One caveat on the numbers: across eight cells ~mask reads 11111011 = 251, but in a real 32-bit int the 1s keep going up to bit 31, so ~(1 << 2) is −5, not 251. It makes no difference to the result — 13 & 251 and 13 & −5 are both 9 — which is why the eight cells are safe to reason in.
Counting the set bits
Now the same shifts and masks, put to work on a real question: how many 1s does n have? The version on the left asks about every position. The version on the right asks once per answer.
Naive — test every position
count = 0for p in 0 .. 31: count = count + ((n >> p) & 1)return count # 32 rounds, every time
Brian Kernighan — clear the lowest 1
count = 0while n != 0: n = n & (n − 1) # kill the lowest 1 count = count + 1return count # one round per set bit
Why n & (n − 1) removes exactly one bit. Subtracting 1 turns the lowest 1 into a 0 and turns every 0 below it into a 1; the bits above it are untouched. ANDing the before and the after therefore keeps every high bit, kills that lowest 1, and kills the borrowed 1s underneath it. On 13: 13 & 12 = 12, 12 & 11 = 8, 8 & 7 = 0 — three rounds, and 13 has three set bits.
Why the naive version cannot catch up. It asks a question about every position, so it costs 32 rounds whether n is 4294967295 or 1. Kernighan's loop asks a question per answer instead of per position, so it costs popcount(n) rounds. The worst case is the same — all 32 bits set — but on the sparse numbers you actually meet it is far cheaper.
The same trick tests a power of two. A power of two has exactly one set bit, so one round of clearing must empty it: n > 0 && (n & (n − 1)) == 0. Keep the guard. 0 has no bits at all and 0 & −1 is 0, and the most negative int has a single set bit too, so both would slip through without it. On our number, 13 & 12 = 12, which is not 0, so 13 is not a power of two.
05 Cheat sheet
The lines worth knowing cold
| Task | One-liner | Cost | On n = 13 |
|---|---|---|---|
| Read bit i | (n >> i) & 1 | O(1) | bit 2 → 1 |
| Set bit i | n | (1 << i) | O(1) | 13 | 4 = 13 |
| Clear bit i | n & ~(1 << i) | O(1) | 13 & 251 = 9 |
| Toggle bit i | n ^ (1 << i) | O(1) | 13 ^ 4 = 9 |
| Clear the lowest 1 | n & (n − 1) | O(1) | 13 → 12 |
| Isolate the lowest 1 | n & −n | O(1) | 13 & −13 = 1 |
| Count set bits, every position | for p in 0..31 | O(w) = 32 | 32 rounds |
| Count set bits, Kernighan | while n: n &= n − 1 | O(popcount) | 3 rounds |
| Power of two? | n > 0 && (n & (n − 1)) == 0 | O(1) | false, 13 & 12 = 12 |
| Multiply or divide by 2^k | n << k · n >> k | O(1) | 26 and 6 for k = 1 |
| Decimal to binary by hand | divide by 2, keep remainders | O(log n) | 1, 0, 1, 1 → read up → 1101 |
06 Where & why
Not faster arithmetic — a different data structure
Bit tricks are not a speed-up for maths. Every compiler already turns x * 2 into a shift, so writing x << 1 yourself buys nothing but a harder line to read. What bit manipulation actually gives you is one integer standing in for a whole set, and an operator that remembers pairs for free.
Reach for it when…
Use something else when…
| Situation | Better choice | Why |
|---|---|---|
| The keys are arbitrary, or the set can grow | Hash set — HashSet, unordered_set, set() | An int mask caps you at 32 members and a long at 64. It cannot hold ids in the thousands at all. |
| You are writing x << 1 for speed | Plain x * 2 | The compiler emits the same instruction either way. You pay in readability and gain O(1) versus O(1). |
| You need a popcount in production code | Integer.bitCount, __builtin_popcount, int.bit_count() | One CPU instruction beats an O(popcount) loop, and it is already tested. Write Kernighan in the interview, call the builtin at work. |
| Order or ranges matter — kth smallest, "at least x" | Sorted array with binary search, or a tree set | A mask has no order. Finding the kth member means walking all O(w) positions, and there is no range query at all. |
07 Interview questions
Twelve you should be able to answer cold
In the order an interview escalates: what the bits are and what the operators do, then the four one-liners, then n & (n − 1) with the two counting methods and the power-of-two test, and finally XOR, shifting, the lowest set bit, and what you would actually ship.
What do people mean by bit manipulation?
Convert 13 to binary and back for me.
What is the difference between OR and XOR?
How do you check whether the i-th bit is set?
How do you set, clear and toggle it?
What does n & (n − 1) do?
Naive bit counting versus Brian Kernighan — what are the complexities?
How do you test whether a number is a power of two, and why the extra guard?
Why does XOR find the element that appears once?
Is n << 1 the same as n * 2?
What does n & −n give you?
When would you actually use bit manipulation in production code?
08 Practice problems
Six problems, in order of bite
Each one is solvable with what is above: the mask 1 << i, the four one-liners, n & (n − 1), n & −n, and the three XOR facts.