Data Structures & Algorithms · Data Structures & Algorithms
Bit Manipulation
Bitwise operations and tricks for masks, counting bits and set operations.
Eight concepts on bitwise work — the five operators, XOR cancel tricks, test/set/clear, power-of-two and lowest-set-bit identities, Brian Kernighan popcount, and bitmasks for subsets. Interview-medium: every claim here is one you can recompute on a tiny integer.
- Data Structures & Algorithms
- Medium level
- 8 concepts
- 5 practice questions
1The five bitwise operators
Bitwise operators act on each bit of an integer. AND (\&) keeps a bit only when both sides have it — the classic mask. OR (|) turns bits on. XOR (\oplus) flips a bit where the other side is 1, so it toggles and also surfaces differences. NOT (\sim) flips every bit. Shifts move the bit pattern: left shift multiplies a non-negative n by a power of two, and arithmetic-style right shift floors a non-negative n by a power of two.
These are not Boolean short-circuit operators. They rewrite the binary representation in place, which is why masks, flags, and compact set encodings all start here.
Figure. Five operators, five jobs: mask, combine, toggle/cancel, invert, and shift — do not treat them as interchangeable.
Pick the operator by job
- Mask or intersectUse AND with a mask whose 1-bits mark the positions you care about.
- Turn bits onUse OR with a mask that has 1s only in the positions to set.
- Toggle or differUse XOR to flip selected bits, or to cancel equal values later.
- Scale by 2^kUse n \ll k or n \gg k on non-negative n instead of multiplying or dividing by 2^k.
| Operator | Per-bit effect | Typical use |
|---|---|---|
| AND (\&) | 1 only if both are 1 | Mask; keep selected bits |
| OR (|) | 1 if either is 1 | Set bits; combine flags |
| XOR (\oplus) | 1 if bits differ | Toggle; cancel duplicates |
| NOT (\sim) | Flip every bit | Build inverted masks |
| Shift \ll / \gg | Move pattern by k places | \times 2^k / \lfloor / 2^k \rfloor (non-negative) |
Shift scales by powers of two
Evaluate 5 \ll 2 and 20 \gg 2 for non-negative integers.
- 5 \ll 2 = 5 \times 2^220
- 20 \gg 2 = \lfloor 20 / 2^2 \rfloor5
Pro tip. Shifts are micro-optimizations for powers of two — prefer a clear multiply when the factor is not a compile-time 2^k.
You need to keep only the lowest 4 bits of n. The right operator pattern is
- n \& 0b1111 — AND with a mask of four 1s
- n | 0b1111 — OR forces those bits on, and leaves higher bits alone incorrectly for a mask
- n \oplus 0b1111 — XOR toggles those bits instead of selecting them
A mask keeps bits where the mask is 1 and clears the rest — that is AND. OR would turn the low four bits on without clearing higher ones as a low-nibble extract; XOR would flip them.
2XOR cancel: find the unpaired number
XOR has two identities that make pair cancellation free: x \oplus x = 0 and x \oplus 0 = x. Folding XOR across an array therefore erases every value that appears an even number of times and leaves a value that appears once. That is the classic Single Number interview pattern — O(n) time and O(1) extra space, with no hash map.
Order does not matter: XOR is associative and commutative, so any traversal order yields the same accumulator.
Figure. XOR cancels pairs: a\oplus a=0, so a fold over a duplicated multiset leaves the singleton.
How the accumulator collapses
- Start at 0result = 0. XOR with 0 leaves the first value unchanged when it arrives.
- Fold every elementresult = result \oplus nums[i] for each index.
- Pairs vanishEach duplicated value meets itself in the fold and becomes 0; the unpaired value remains.
Single Number
def single_number(nums):
result = 0
for x in nums:
result ^= x
return resultSingle Number
Given nums = [4, 1, 2, 1, 2] where every element appears twice except one, find the unique element.
- 0 \oplus 44
- 4 \oplus 15
- 5 \oplus 27
- 7 \oplus 16
- 6 \oplus 24
Pro tip. XOR gives an O(n) time, O(1) space solution — no hash map or sorting needed. The same fold fails if some value appears three times; that needs a different bit-count trick.
In an array where every value appears twice except one that appears once, XOR of all elements equals
- The unpaired value, because each pair cancels to 0
- 0 always, because XOR of any list is 0
- The count of distinct values
x \oplus x = 0 removes pairs; x \oplus 0 = x leaves the singleton. The whole fold is not always 0 — that would require an even count of every value.
3Test, set, and clear bit i
Bit index i counts from the least-significant bit as 0. Test bit i with (n \gg i) \& 1. Set it with n | (1 \ll i). Clear it with n \& \sim(1 \ll i) — the inverted one-hot mask keeps every bit except i.
The one-hot 1 \ll i is the whole trick: build a mask with a single 1, then AND, OR, or AND-NOT against n. Getting the index off-by-one is the usual bug — index zero is the units bit, not the leftmost drawn digit.
Figure. Four cells show 1101_2 = 13. Index i grows right-to-left from the least-significant bit; bit 1 is the 0 cell.
Three masks from one-hot
- Build 1 \ll iA mask with only bit i set.
- TestShift n down by i and AND with 1 — result is 0 or 1.
- Set or clearOR with the one-hot to set; AND with its NOT to clear.
Edit bits of 13
Start from n = 13 (1101_2). Test bit 1, set bit 1, then clear bit 2 of the original 13.
- (13 \gg 1) \& 10
- 13 | (1 \ll 1)15 (1111_2)
- 13 \& \sim(1 \ll 2)9 (1001_2)
Pro tip. Set and clear both start from the same one-hot mask; only the combining operator changes.
To clear bit i of n you compute
- n \& \sim(1 \ll i)
- n | (1 \ll i)
- (n \gg i) \& 1
AND with the inverted one-hot forces bit i to 0 and leaves other bits alone. OR sets the bit; the shift-and-AND form only tests it.
4Power-of-two test
A positive integer n is a power of two if and only if it has exactly one bit set. Clearing the lowest set bit with n \& (n - 1) therefore yields 0 exactly on those values: the single 1 disappears and nothing remains. The full test is n > 0 and n \& (n - 1) = 0 — without the positivity check, zero falsely looks like a power of two.
That is why the identity shows up in allocator alignment checks and in "is this length a power of two?" interview prompts.
Figure. A power of two is a single 1 among zeros. Clearing that 1 with n \& (n-1) leaves an all-zero word.
Apply the test
- Reject non-positiveIf n \le 0, it is not a power of two.
- Clear lowest 1Compute n \& (n - 1). Subtracting 1 flips the trailing 0s and the lowest 1.
- Demand zeroIf the result is 0, the original n had exactly one set bit.
8 yes, 6 no
Decide whether 8 and 6 are powers of two using n \& (n - 1).
- 8 \& (8 - 1) = 8 \& 70 → yes (n > 0)
- 6 \& (6 - 1) = 6 \& 54 → no
Pro tip. Always keep the n > 0 guard beside the bit test — 0 \& (0 - 1) is an implementation-defined footgun if you only quote the AND.
Which check correctly identifies powers of two among non-negative ints?
- n > 0 and n \& (n - 1) = 0
- n \& (n - 1) = 0 alone, including n = 0
- n | (n - 1) = n
Exactly one set bit plus positivity. Dropping n > 0 accepts 0. The OR identity is not the standard power-of-two test.
5Clear or isolate the lowest set bit
Two identities manipulate the least-significant 1 without scanning all bits. n \& (n - 1) clears that lowest set bit and leaves every higher bit unchanged. n \& (-n) isolates it — on two's-complement integers the negation flips bits above the lowest 1, so the AND keeps only that one-hot value.
Clearing is the engine inside Brian Kernighan's popcount; isolating is the engine inside "rightmost flag" loops and some Fenwick-tree index steps.
Figure. Three rows for n = 12 (1100_2): source, clear-lowest (1000_2 = 8), and isolate-lowest (0100_2 = 4). Clear removes the bit from n; isolate extracts it as a one-hot mask.
Same n, two results
- Clearn \& (n - 1) turns the lowest 1 into 0.
- Isolaten \& (-n) returns a value with only that lowest 1 set.
- Do not confuse themClear removes the bit from n; isolate extracts the bit as its own mask.
| Expression | Result | Use |
|---|---|---|
| n \& (n - 1) | n with lowest 1 cleared | Popcount loop; strip flags one by one |
| n \& (-n) | One-hot mask of lowest 1 | Rightmost flag; tree index steps |
Lowest bit of 12
For n = 12 (1100_2), clear the lowest set bit and isolate it.
- 12 \& (12 - 1) = 12 \& 118 (1000_2)
- 12 \& (-12)4 (0100_2)
Pro tip. After a clear, the new lowest set bit is the next 1 to the left — that is why repeating clear walks every set bit.
n \& (-n) returns
- A one-hot mask of the lowest set bit of n
- n with the lowest set bit cleared
- Always 0
Isolate keeps only the lowest 1. Clearing is n \& (n - 1), not n \& (-n).
6Count set bits in O(popcount)
Brian Kernighan's loop counts 1-bits by repeatedly clearing the lowest set bit: while n \neq 0, do n \&= (n - 1) and increment a counter. Each iteration removes exactly one 1, so the loop runs once per set bit — O(\mathrm{popcount}(n)), not O(32) or O(64) for a fixed word width.
When the density of 1s is low, that beats scanning every bit position. Hardware `popcount` instructions are faster still when available; the loop is the portable interview form of the same idea.
Reuse the clear-lowest strip from Clear or isolate the lowest set bit: each x\ &=\ x-1 clears one set bit; the loop count equals the popcount.
Walk the 1s
- Count at 0Initialize count = 0.
- Clear lowest 1Replace n with n \& (n - 1) and add 1 to count.
- Stop at 0When n becomes 0 every set bit has been removed; return count.
Brian Kernighan popcount
def popcount(n):
count = 0
while n:
n &= n - 1
count += 1
return countCount set bits in 13
Count the number of 1 bits in the binary representation of 13 (1101_2).
- 13 \& 1212 (1100_2), count = 1
- 12 \& 118 (1000_2), count = 2
- 8 \& 70, count = 3
Pro tip. n \&= (n-1) clears the lowest set bit, so the loop runs only as many times as there are 1s, not for all 32 bits.
Brian Kernighan's popcount on a word with k set bits runs in
- O(k) iterations — one clear per set bit
- O(32) always, regardless of k
- O(\log n) binary-search steps over bit positions
Each iteration clears one 1. A fixed 32-step scan is the naive width walk; there is no binary search over bit indices here.
7Bitmasks as subsets
A bitmask packs a subset of up to about 32 or 64 indexed elements into one integer: each bit flags whether that index is in the subset. Membership is (mask \gg i) \& 1. Union is OR, intersection is AND, and symmetric difference is XOR — each O(1) on a machine word.
That encoding is why subset DP and many combinatorial searches store state as an integer rather than as a hash set of indices.
Figure. Three cells for a 3-element universe. Mask 0b101 has item 0 and item 2 in the subset; item 1 is absent.
Set algebra on bits
- EncodeTurn membership of index i into bit i of an integer mask.
- CombineUnion |, intersection \&, toggle membership with XOR against 1 \ll i.
- QueryTest bit i for membership in O(1).
| Set operation | Bit expression | Cost |
|---|---|---|
| Membership of i | (mask \gg i) \& 1 | O(1) |
| Union | a | b | O(1) |
| Intersection | a \& b | O(1) |
| Add / remove i | OR / AND-NOT with 1 \ll i | O(1) |
Mask 0b101 on three items
Items are indexed 0, 1, 2. Mask 0b101 encodes a subset. Which indices are present, and what are 0b101 | 0b010 and 0b101 \& 0b110?
- bits of 0b101{0, 2} present; 1 absent
- 0b101 | 0b0100b111 = {0, 1, 2}
- 0b101 \& 0b1100b100 = {2}
Pro tip. Represent small subsets as an integer bitmask to make union, intersection, and membership tests O(1).
Intersection of two bitmasks is
- AND — bits that are 1 in both masks
- OR — bits that are 1 in either mask
- XOR — bits that differ
Intersection keeps elements in both sets, which is AND on membership bits. OR is union; XOR is symmetric difference.
8Enumerate masks: subset DP shape
Once subsets are integers from 0 to 2^n - 1, many DP transitions scan every mask and every element: for mask in 0 \ldots 2^n - 1, for i in 0 \ldots n - 1, update a state from mask with bit i flipped on or off. That double loop is the standard subset-DP budget — exponential in n, times a linear scan of bit positions — when n is about 20 or less.
The bit encoding is what makes the outer index a contiguous integer range. Without masks you would hash sets; with masks the array dp[1 \ll n] is enough.
Figure. For n=3 the outer index is the eight masks 0…7. On the current mask the inner loop visits each of the n bit positions — here three visits under mask 5. Interview n near 20 is the same shape, not a longer row.
The O(2^n n) skeleton
- Allocate by maskIndex DP by the integer mask, size 2^n.
- Scan masksIterate mask from 0 to 2^n - 1 (often in an order that respects transitions).
- Try each bitFor each i, read or write the neighbour mask with bit i changed — n work per mask.
Scan all masks and bits
def for_each_mask_bit(n, visit):
for mask in range(1 << n):
for i in range(n):
visit(mask, i, (mask >> i) & 1)Iterating every subset mask and every element index for n items costs
- O(2^n \times n)
- O(n^2)
- O(2^n) with no factor of n
2^n masks and n bit positions per mask. Dropping the inner loop undercounts; an n^2 bound ignores the exponential mask space.
Notes
- Bitwise Operators: AND (&) masks bits, OR (|) sets bits, XOR (^) toggles/finds differences, NOT (~) flips, and shifts (<<, >>) multiply or divide by powers of two.
- XOR Properties: x ^ x = 0 and x ^ 0 = x, so XORing all elements cancels pairs and isolates a single unpaired number.
- Check/Set/Clear a Bit: Test bit i with (n >> i) & 1, set it with n | (1 << i), and clear it with n & ~(1 << i).
- Power of Two Test: A positive n is a power of two iff n & (n-1) == 0, because it has exactly one set bit.
- Bitmasking: A bitmask represents a subset of up to ~32/64 elements as bits, enabling subset DP and fast set operations.
Formulas
- Left shift: n << k = n \times 2^k; right shift: n >> k = \lfloor n / 2^k \rfloor for non-negative n.
- Clear the lowest set bit: n & (n-1); isolate the lowest set bit: n & (-n).
- Counting set bits (Brian Kernighan): O(\text{number of set bits}) via repeated n &= (n-1).
- Power of two: n > 0 and n & (n-1) == 0.
- Subset bitmask DP: O(2^n \times n) over all subsets of n items.
Exam traps & shortcuts
- To find the single non-duplicated number when all others appear twice, XOR the whole array - pairs cancel to 0.
- Use n & (n-1) to clear the lowest set bit; looping this counts set bits in O(popcount) time.
- Multiply/divide by powers of two with shifts (x << 3 for x*8) when micro-optimizing.
- Represent small subsets as an integer bitmask to make union (|), intersection (&), and membership tests O(1).
Reference tables
Restated from the concepts above — scan sheet, not a second argument.
| Identity | Form | Use |
|---|---|---|
| Left / right shift | n \ll k = n \times 2^k; n \gg k = \lfloor n / 2^k \rfloor (non-negative n) | Scale by powers of two |
| Clear lowest set bit | n \& (n - 1) | Popcount; strip flags |
| Isolate lowest set bit | n \& (-n) | One-hot of rightmost 1 |
| Power of two | n > 0 and n \& (n - 1) = 0 | Alignment / size checks |
| Subset DP scan | O(2^n \times n) over masks and bits | DP on subsets of n items |
Which operator for which job — same roster as the opening concept.
| Job | Pattern |
|---|---|
| Mask / keep bits | n \& \mathrm{mask} |
| Set bit i | n | (1 \ll i) |
| Clear bit i | n \& \sim(1 \ll i) |
| Test bit i | (n \gg i) \& 1 |
| Cancel pairs | Fold XOR across the array |
| Subset union / intersection | a | b / a \& b |
Recap
Read only this the night before a bit-manipulation round.
- Ops
- AND masks, OR sets, XOR toggles/cancels, NOT flips, shifts scale by 2^k.
- XOR
- x \oplus x = 0, x \oplus 0 = x — fold the array to find the unpaired value.
- Bit i
- Test (n \gg i)\&1; set n|(1\ll i); clear n\&\sim(1\ll i).
- Power of two
- n > 0 and n\&(n-1)=0 — exactly one set bit.
- Lowest 1
- Clear with n\&(n-1); isolate with n\&(-n).
- Popcount
- Kernighan: loop n\&=(n-1) — O(\mathrm{popcount}).
- Bitmask
- Bit i ↔ element i; union |, intersection \&; subset DP O(2^n n).
Practise Bit Manipulation
Reading is free and needs no account. Practice, mocks and progress live in the app.
- 5 exam-style questions on this topic, with explanations
- A 6-question practice set that ends the chapter
- Timed mocks scored with the real marking scheme
- Readiness tracked per topic, kept on your device