Bit Manipulation — The Complete Guide
Master bit manipulation from first principles. Learn XOR tricks, bitmask techniques, and bit-level operations to solve problems in O(1) space and impress interviewers with elegant solutions.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
Every number in a computer is stored as a sequence of bits — 0s and 1s. The number 5 is 101 in binary, 13 is 1101. Bit manipulation means operating directly on these individual bits using special operators: AND, OR, XOR, NOT, and shifts.
Why bother? Because bit operations are the fastest operations a CPU can perform — they happen in a single clock cycle. And certain problems have incredibly elegant solutions when you think at the bit level. Finding a unique number among duplicates? One line with XOR. Checking if a number is a power of 2? One line with AND. Generating all subsets? A loop with bitmasks.
Bit manipulation isn't about memorizing tricks — it's about understanding what each operator does to individual bits, and then recognizing when a problem's structure maps to bit-level operations.
The Operators — Your Toolkit
AND (&) — Both bits must be 1 → 1. Otherwise → 0. 1010 & 1100 = 1000 Use: Check if a bit is set, clear bits, mask bits OR (|) — At least one bit is 1 → 1. Both 0 → 0. 1010 | 1100 = 1110 Use: Set a bit, combine flags XOR (^) — Bits are different → 1. Same → 0. 1010 ^ 1100 = 0110 Use: Find unique element, toggle bits, swap without temp NOT (~) — Flip every bit. 0→1, 1→0. ~1010 = 0101 (simplified; actual result depends on bit width) Use: Invert a mask LEFT SHIFT (<<) — Shift bits left, fill with 0s. Equivalent to ×2. 0011 << 1 = 0110 (3 → 6) Use: Multiply by powers of 2, create bit masks RIGHT SHIFT (>>) — Shift bits right, drop rightmost. Equivalent to ÷2. 0110 >> 1 = 0011 (6 → 3) Use: Divide by powers of 2, check individual bits
Analogies to Build Intuition
Light Switches on a Panel
Imagine a row of light switches (bits). AND checks if BOTH switches are on. OR checks if EITHER is on. XOR checks if they're in DIFFERENT positions. NOT flips every switch. Bit manipulation is just flipping, checking, and combining switches.
XOR as a 'Cancel Out' Operator
XOR is like a toggle: applying it twice cancels out. If you XOR a number with itself, you get 0 (it cancels). If you XOR 0 with any number, you get that number back. This is why XOR finds the unique element — all duplicates cancel each other out.
Bitmask as a Checklist
A bitmask is a number where each bit represents a yes/no flag. Bit 0 = 'visited city A?', bit 1 = 'visited city B?', etc. The number 5 (101) means 'visited A and C but not B.' Bitmasks let you represent sets of items as a single integer.
Essential Bit Tricks — Memorize These
// Check if bit i is set (1) (n >> i) & 1 // or: n & (1 << i) // Set bit i to 1 n | (1 << i) // Clear bit i (set to 0) n & ~(1 << i) // Toggle bit i n ^ (1 << i) // Check if n is a power of 2 n > 0 && (n & (n - 1)) === 0 // Get the lowest set bit (rightmost 1) n & (-n) // or: n & ~(n - 1) // Clear the lowest set bit n & (n - 1) // Count set bits (Brian Kernighan's) let count = 0; while (n) { n &= (n - 1); count++; } // Check if n is even/odd (n & 1) === 0 // even (n & 1) === 1 // odd
The core insight
Bit manipulation gives you O(1) operations on individual bits within a number. XOR cancels duplicates. AND masks and checks. OR sets flags. n & (n-1) clears the lowest set bit. These primitives combine to solve problems that would otherwise need hash maps, sorting, or extra space.
Pattern Recognition
Bit manipulation problems rarely say "use bits." They hide behind constraints like "O(1) space" or "without extra memory." Here are the signals.
Keywords & Signals in the Problem Statement
Use Bit Manipulation when you see...
- ✅"Every element appears twice except one" → XOR
- ✅"O(1) extra space" on a duplicate/missing number problem
- ✅"Power of 2" or "power of 4"
- ✅"Count the number of 1 bits" (Hamming weight)
- ✅"Hamming distance" between two numbers
- ✅"Generate all subsets" of a set with n ≤ 20
- ✅"Swap two numbers without a temp variable"
- ✅"Reverse bits" of an integer
- ✅"Single number" among duplicates
- ✅Constraints mention 32-bit integers or bit-level operations
Don't use Bit Manipulation when...
- ❌The problem involves floating-point numbers (bits don't map cleanly)
- ❌You need to find pairs with a specific sum (use two pointers or hashing)
- ❌The problem is about string manipulation (usually not bit-related)
- ❌n is large (> 20-25) and you need bitmask DP (2^n states explode)
- ❌The problem involves sorting or ordering (bits don't help with order)
- ❌A simpler approach (hash map, math) is more readable and equally fast
Bit Manipulation vs. Similar Approaches
| Approach | When to Use | Key Difference |
|---|---|---|
| XOR trick | Find unique element among duplicates, O(1) space | XOR cancels pairs: a ^ a = 0. No extra memory needed. |
| Hash Map | Count frequencies, find duplicates, general lookups | O(n) space. More flexible but uses extra memory. |
| Bitmask (subset enumeration) | Generate all subsets, bitmask DP (n ≤ 20) | Represent sets as integers. 2^n states. Compact and fast. |
| Backtracking | Generate subsets/combinations for larger n | More flexible, handles constraints. Bitmask is faster for small n. |
| Math tricks | Missing number (sum formula), power checks | Sometimes simpler than bits. Use whichever is cleaner. |
The mental test
Ask yourself: "Does this problem involve finding something unique among duplicates? Checking properties of binary representation? Representing subsets compactly? Operating without extra space?" If yes, think bits. The strongest signal is "O(1) space" combined with duplicate/missing number problems.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using a classic example: Single Number — every element in an array appears twice except one. Find the unique element.
Brute Force — Nested Loop
For each element, scan the entire array to see if it appears again. If not, it's the unique one. This works but is painfully slow.
function singleNumber(nums: number[]): number { for (let i = 0; i < nums.length; i++) { let found = false; for (let j = 0; j < nums.length; j++) { if (i !== j && nums[i] === nums[j]) { found = true; break; } } if (!found) return nums[i]; } return -1; } // O(n²) time, O(1) space. Too slow for large inputs.
Better — Hash Map
Count frequencies with a hash map. The element with count 1 is the answer. Fast, but uses O(n) extra space.
function singleNumber(nums: number[]): number { const freq = new Map<number, number>(); for (const n of nums) { freq.set(n, (freq.get(n) ?? 0) + 1); } for (const [num, count] of freq) { if (count === 1) return num; } return -1; } // O(n) time, O(n) space. Can we do O(1) space?
Optimal — XOR Everything
XOR has a magical property: a ^ a = 0 and a ^ 0 = a. If you XOR all elements together, every pair cancels out (becomes 0), and only the unique element remains. One pass, no extra memory.
function singleNumber(nums: number[]): number { let result = 0; for (const n of nums) { result ^= n; } return result; } // [4, 1, 2, 1, 2] → 4^1^2^1^2 = (1^1)^(2^2)^4 = 0^0^4 = 4 // O(n) time, O(1) space. Perfect.
Why Does the Optimization Work?
XOR Properties That Make This Work
Three properties: (1) a ^ a = 0 (self-cancellation). (2) a ^ 0 = a (identity). (3) XOR is commutative and associative (order doesn't matter). Together: XOR-ing all elements cancels every pair, leaving only the unique one.
From Hash Map to XOR
The hash map tracks 'which elements have been seen.' XOR does the same thing implicitly — it 'remembers' elements that have appeared an odd number of times and 'forgets' elements that have appeared an even number of times. It's a 1-bit frequency counter per bit position.
The General Principle
Bit manipulation optimizations work when the problem's structure aligns with bit-level operations. 'Find the unique among pairs' maps perfectly to XOR's cancellation property. Not every problem has a bit solution — but when it does, it's usually the most elegant one.
The thinking process matters more than the code
In interviews, explain: "XOR has the property that a ^ a = 0 and a ^ 0 = a. If I XOR all elements, every duplicate pair cancels to 0, and only the unique element survives. This gives me O(n) time and O(1) space." The interviewer wants to see you understand WHY it works.
Core Templates
Bit manipulation problems fall into a few reusable patterns. Memorize these templates — they cover the vast majority of interview questions.
Template 1: XOR to Find Unique Elements
XOR all elements. Pairs cancel out, leaving the unique one(s). Works when every element appears an even number of times except the target.
// Single unique element (all others appear twice) function findUnique(nums: number[]): number { let xor = 0; for (const n of nums) xor ^= n; return xor; } // TWO unique elements (all others appear twice) function findTwoUnique(nums: number[]): [number, number] { // Step 1: XOR all → gives xor of the two unique numbers let xor = 0; for (const n of nums) xor ^= n; // Step 2: Find any set bit (the two numbers differ here) const diffBit = xor & (-xor); // lowest set bit // Step 3: Split into two groups by that bit, XOR each group let a = 0, b = 0; for (const n of nums) { if (n & diffBit) a ^= n; else b ^= n; } return [a, b]; } // Why this works: the two unique numbers differ in at least one bit. // Splitting by that bit puts them in different groups. // Within each group, all pairs still cancel, leaving one unique each.
Template 2: Bit Counting (Brian Kernighan's)
Count the number of set bits (1s) in a number. The trick: n & (n-1) clears the lowest set bit. Repeat until n is 0.
// Count set bits (Hamming weight) function countBits(n: number): number { let count = 0; while (n) { n &= (n - 1); // clear lowest set bit count++; } return count; } // Why n & (n-1) clears the lowest set bit: // n = ...1000 (lowest set bit is at position 3) // n-1 = ...0111 (borrows from that bit, flips everything below) // n&n-1 = ...0000 (the lowest set bit is gone) // // This runs in O(k) where k = number of set bits, not O(32). // Hamming distance between two numbers function hammingDistance(x: number, y: number): number { return countBits(x ^ y); // XOR gives 1s exactly where x and y differ }
Template 3: Power of 2 Check
A power of 2 has exactly one set bit: 1, 10, 100, 1000... So n & (n-1) === 0 (clearing the only set bit gives 0).
function isPowerOfTwo(n: number): boolean { return n > 0 && (n & (n - 1)) === 0; } // 8 = 1000, 7 = 0111 → 1000 & 0111 = 0000 → true // 6 = 0110, 5 = 0101 → 0110 & 0101 = 0100 → false (not power of 2) // Power of 4: power of 2 AND set bit is at an even position function isPowerOfFour(n: number): boolean { return n > 0 && (n & (n - 1)) === 0 && (n & 0x55555555) !== 0; // 0x55555555 = 01010101... (bits at even positions) }
Template 4: Bitmask Subset Enumeration
Use an integer as a bitmask where bit i represents whether element i is included. Iterate from 0 to 2^n - 1 to generate all subsets.
function subsets(nums: number[]): number[][] { const n = nums.length; const result: number[][] = []; for (let mask = 0; mask < (1 << n); mask++) { const subset: number[] = []; for (let i = 0; i < n; i++) { if (mask & (1 << i)) { // bit i is set → include nums[i] subset.push(nums[i]); } } result.push(subset); } return result; } // For nums = [1, 2, 3]: // mask=0 (000) → [] // mask=1 (001) → [1] // mask=2 (010) → [2] // mask=3 (011) → [1, 2] // mask=4 (100) → [3] // mask=5 (101) → [1, 3] // mask=6 (110) → [2, 3] // mask=7 (111) → [1, 2, 3] // Only works for n ≤ ~20 (2^20 ≈ 1 million states)
Template 5: Bit-by-Bit Construction
Build the answer one bit at a time, from the most significant bit to the least (or vice versa). Used for problems like Maximum XOR, Reverse Bits.
// Reverse bits of a 32-bit integer function reverseBits(n: number): number { let result = 0; for (let i = 0; i < 32; i++) { result = (result << 1) | (n & 1); // shift result left, add lowest bit of n n >>>= 1; // shift n right (unsigned) } return result >>> 0; // convert to unsigned 32-bit } // Process one bit at a time: extract from n, place into result. // This pattern works for any "transform bits" problem.
Which template to pick?
Find unique among duplicates? → Template 1 (XOR). Count bits or Hamming distance? → Template 2 (Kernighan's). Power of 2/4 check? → Template 3 (n & (n-1)). Generate all subsets (n ≤ 20)? → Template 4 (bitmask). Transform or reverse bits? → Template 5 (bit-by-bit).
Variations & Sub-Patterns
The templates cover the basics, but real interview problems add twists. Here are the most important variations.
| Variation | Core Technique | Classic Example |
|---|---|---|
| Single unique (pairs) | XOR all elements → unique remains | Single Number (LC 136) |
| Two unique (pairs) | XOR all → split by differing bit → XOR each group | Single Number III (LC 260) |
| Unique among triplets | Count bits mod 3 at each position | Single Number II (LC 137) |
| Count set bits | Brian Kernighan's: n &= (n-1) until 0 | Number of 1 Bits (LC 191) |
| Counting bits for range | DP: countBits[i] = countBits[i >> 1] + (i & 1) | Counting Bits (LC 338) |
| Power checks | n & (n-1) === 0 for power of 2; add mask for power of 4 | Power of Two (LC 231), Power of Four (LC 342) |
| Reverse / rotate bits | Bit-by-bit extraction and placement | Reverse Bits (LC 190) |
| Missing number | XOR indices and values — missing one doesn't cancel | Missing Number (LC 268) |
| Bitmask DP | State = bitmask of visited items, transition = add/remove item | Partition to K Equal Sum Subsets (LC 698) |
| Subset enumeration | Loop 0 to 2^n - 1, check each bit for inclusion | Subsets (LC 78) |
Deep Dive: Single Number II (Every Element Appears Three Times)
XOR alone doesn't work here because a ^ a ^ a = a (odd count doesn't cancel). Instead, count the number of set bits at each position across all numbers. If the count mod 3 is not 0, that bit belongs to the unique number.
Problems mentioned above
function singleNumber(nums: number[]): number { let result = 0; for (let bit = 0; bit < 32; bit++) { let sum = 0; for (const n of nums) { sum += (n >> bit) & 1; // count how many numbers have this bit set } if (sum % 3 !== 0) { result |= (1 << bit); // this bit belongs to the unique number } } return result; } // For each of the 32 bit positions, count how many numbers have a 1. // If count % 3 ≠ 0, the unique number has a 1 at that position. // This generalizes: for "appears k times except one", use count % k.
Deep Dive: Missing Number (XOR with Indices)
Array contains n numbers from 0 to n with one missing. XOR all array values with all indices 0 to n. Every number that's present cancels with its index. The missing number is left.
function missingNumber(nums: number[]): number { let xor = nums.length; // start with n (the last index) for (let i = 0; i < nums.length; i++) { xor ^= i ^ nums[i]; // XOR index and value } return xor; } // nums = [3, 0, 1], n = 3 // xor = 3 ^ (0^3) ^ (1^0) ^ (2^1) // = 3 ^ 3 ^ 0 ^ 1 ^ 0 ^ 2 ^ 1 // = (3^3) ^ (0^0) ^ (1^1) ^ 2 // = 0 ^ 0 ^ 0 ^ 2 = 2 ← the missing number
Generalizing the XOR trick
XOR works for "find the odd one out" whenever elements appear in pairs (or any even count). For triplets, use bit counting mod 3. For two unique elements, split by a differing bit. The core idea is always the same: exploit cancellation properties.
Visual Walkthrough
Let's trace through key bit operations to build muscle memory for how bits behave.
XOR Single Number — Step by Step
Input: [4, 1, 2, 1, 2] Binary representations: 4 = 100 1 = 001 2 = 010 XOR accumulation: result = 000 (start with 0) result ^= 4: 000 ^ 100 = 100 result ^= 1: 100 ^ 001 = 101 result ^= 2: 101 ^ 010 = 111 result ^= 1: 111 ^ 001 = 110 ← the 1 cancels result ^= 2: 110 ^ 010 = 100 ← the 2 cancels result = 100 = 4 ✓ Key: XOR is commutative, so order doesn't matter. Pairs cancel: 1^1 = 0, 2^2 = 0. Only 4 remains.
n & (n-1) — Clearing the Lowest Set Bit
Why does n & (n-1) clear the lowest set bit? Example: n = 12 = 1100 n = 1100 n - 1 = 1011 (subtracting 1 flips the lowest set bit and all bits below it) n & (n-1) = 1000 (the lowest set bit at position 2 is cleared!) Another example: n = 10 = 1010 n = 1010 n - 1 = 1001 n & (n-1) = 1000 ✓ (lowest set bit at position 1 cleared) Power of 2 check: n = 8 = 1000 n = 1000 n - 1 = 0111 n & (n-1) = 0000 → equals 0 → IS a power of 2 ✓ Non-power: n = 6 = 0110 n = 0110 n - 1 = 0101 n & (n-1) = 0100 → NOT 0 → NOT a power of 2 ✓ Brian Kernighan's bit counting: n = 13 = 1101 Iteration 1: 1101 & 1100 = 1100 (count=1) Iteration 2: 1100 & 1011 = 1000 (count=2) Iteration 3: 1000 & 0111 = 0000 (count=3) n = 0, stop. Answer: 3 set bits ✓
Bitmask Subset Enumeration — Step by Step
nums = [a, b, c] (n = 3, so 2^3 = 8 subsets) mask binary bits set subset ──── ────── ───────── ────── 0 000 none [] 1 001 bit 0 [a] 2 010 bit 1 [b] 3 011 bits 0,1 [a, b] 4 100 bit 2 [c] 5 101 bits 0,2 [a, c] 6 110 bits 1,2 [b, c] 7 111 bits 0,1,2 [a, b, c] For each mask, check each bit: mask = 5 (101): bit 0: 101 & 001 = 001 ≠ 0 → include a bit 1: 101 & 010 = 000 = 0 → skip b bit 2: 101 & 100 = 100 ≠ 0 → include c → subset = [a, c] ✓ This is equivalent to backtracking but uses integer arithmetic. Faster for small n (≤ 20) because no recursion overhead.
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different bit manipulation technique. Solve them in order.
LC 136. Single Number
Why this pattern:
The purest XOR problem. Every element appears twice except one. XOR all elements — pairs cancel, unique remains. This is the foundation for all XOR-based problems.
Key idea: result = 0; for each n: result ^= n. The answer is result. One line of logic. Understand WHY it works: a ^ a = 0, a ^ 0 = a, XOR is commutative and associative.
LC 191. Number of 1 Bits (Hamming Weight)
Why this pattern:
Bit counting with Brian Kernighan's trick. n &= (n-1) clears the lowest set bit. Count how many times you can do this before n becomes 0.
Key idea: While n ≠ 0: n &= (n-1), count++. This runs in O(k) where k = number of set bits, not O(32). Understand why n & (n-1) clears the lowest set bit — it's the most important bit trick.
LC 338. Counting Bits
Why this pattern:
DP using the bit trick. For each number i from 0 to n, count its set bits. The recurrence: countBits[i] = countBits[i >> 1] + (i & 1). Shifting right removes the last bit; (i & 1) checks if it was set.
Key idea: dp[i] = dp[i >> 1] + (i & 1). The number of set bits in i = set bits in i/2 (shift right) + whether the last bit was 1. This gives O(n) time instead of O(n × 32).
LC 268. Missing Number
Why this pattern:
XOR with indices. Array has n numbers from 0 to n, one missing. XOR all values with all indices 0 to n. Present numbers cancel with their index. The missing one doesn't cancel.
Key idea: xor = n (start with the last index). For each i: xor ^= i ^ nums[i]. Every present number cancels with its index. The missing number has no partner → it survives. Alternative: sum formula (n×(n+1)/2 - sum).
LC 190. Reverse Bits
Why this pattern:
Bit-by-bit construction (Template 5). Extract the lowest bit of n, place it into the result from the other end. Repeat 32 times.
Key idea: For 32 iterations: result = (result << 1) | (n & 1); n >>>= 1. Each step takes the rightmost bit of n and appends it to result. After 32 steps, result has all bits reversed.
LC 260. Single Number III
Why this pattern:
Two unique elements among pairs. XOR all → get XOR of the two unique numbers. Find a differing bit (lowest set bit). Split all numbers into two groups by that bit. XOR each group → one unique per group.
Key idea: Step 1: xor = XOR of all. Step 2: diffBit = xor & (-xor). Step 3: Split and XOR each group. The differing bit guarantees the two unique numbers land in different groups. Pairs still cancel within each group.
LC 137. Single Number II
Why this pattern:
Every element appears three times except one. XOR alone doesn't work (a^a^a = a). Instead, count set bits at each position mod 3. If count % 3 ≠ 0, that bit belongs to the unique number.
Key idea: For each of 32 bit positions: count how many numbers have that bit set. If count % 3 ≠ 0, set that bit in the result. This generalizes to 'appears k times' by using count % k.
Practice strategy
For each problem: (1) Try the brute force first (hash map or nested loop). (2) Ask: "Can I use XOR cancellation? Bit counting? Bitmask?" (3) Identify which template applies. (4) After solving, write the key bit property that makes it work.
Common Mistakes
These are the traps that catch people on bit manipulation problems. Learn them here so you don't learn them in an interview.
Confusing signed and unsigned right shift
In JavaScript/TypeScript, >> is signed (preserves the sign bit) and >>> is unsigned (fills with 0). Using >> on negative numbers gives unexpected results because the sign bit propagates.
✅For bit manipulation problems, use >>> (unsigned right shift) when working with 32-bit integers. Especially important in Reverse Bits and Number of 1 Bits. Also use >>> 0 to convert to unsigned 32-bit.
Forgetting that XOR only works for pairs
You try to use XOR to find a unique element when others appear three times. a ^ a ^ a = a, not 0. XOR only cancels elements that appear an EVEN number of times.
✅XOR works when duplicates appear in pairs (2, 4, 6... times). For triplets, use bit counting mod 3. For k copies, use bit counting mod k. Always check: does the duplicate count cancel with XOR?
Off-by-one in bit positions
You use (1 << 32) in JavaScript, which overflows because JS bitwise operations work on 32-bit signed integers. 1 << 31 gives -2147483648 (the sign bit), not 2^31.
✅JavaScript bitwise operations are 32-bit signed. The valid range for shifts is 0-31. For bit 31, be careful with the sign. Use >>> 0 to convert to unsigned when needed.
Using bitmask DP when n is too large
You try bitmask DP with n = 25, creating 2^25 ≈ 33 million states. This is too slow and uses too much memory. Bitmask DP is practical only for n ≤ 20 (about 1 million states).
✅Check the constraint on n. If n ≤ 15-20, bitmask DP is fine. If n > 20, use backtracking with pruning, or find a different approach entirely. 2^20 ≈ 1M is the practical limit.
Operator precedence surprises
You write if (n & 1 === 0) expecting to check if n is even. But === has higher precedence than &, so it evaluates as n & (1 === 0) = n & false = n & 0 = 0. Always false.
✅ALWAYS use parentheses with bitwise operators: if ((n & 1) === 0). Bitwise operators have lower precedence than comparison operators in most languages. This is the #1 bit manipulation bug.
Not handling negative numbers or zero
You check isPowerOfTwo(0) and get true because 0 & (0-1) = 0 & (-1) = 0. But 0 is not a power of 2. Or you forget that negative numbers can't be powers of 2.
✅Always add n > 0 to power-of-2 checks: n > 0 && (n & (n-1)) === 0. Zero and negative numbers are never powers of 2. Test your solution with 0, -1, and 1.
Interview Strategy
Bit manipulation problems test whether you understand low-level operations and can think creatively. Here's how to approach them.
The 5-Step Interview Flow
Start with the Obvious Approach
'I can solve this with a hash map in O(n) time and O(n) space.' — Always state the straightforward solution first. This shows you can solve the problem even without bit tricks.
Mention the Space Constraint
'But the problem asks for O(1) space. That makes me think of bit manipulation — specifically XOR, since it can find unique elements without extra memory.' — Connect the constraint to the technique.
Explain the Bit Property
'XOR has the property that a ^ a = 0 and a ^ 0 = a. If I XOR all elements, pairs cancel out and only the unique element remains.' — State the specific property you're using and WHY it works.
Code It Clean
Bit manipulation code is naturally concise. Use parentheses around bitwise operations. Add a comment for the key trick (e.g., '// n & (n-1) clears lowest set bit'). Keep it readable.
Test with Binary Examples
Trace through with small numbers in binary: '4 = 100, 1 = 001, 2 = 010. XOR: 100 ^ 001 = 101, 101 ^ 010 = 111, 111 ^ 001 = 110, 110 ^ 010 = 100 = 4.' Binary traces make bit operations concrete.
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "What if elements appear three times instead of two?" | XOR doesn't work for odd counts. Use bit counting: for each of 32 positions, count set bits mod 3. Non-zero remainder → that bit belongs to the unique number. |
| "What if there are TWO unique elements?" | XOR all → get XOR of the two uniques. Find a differing bit (lowest set bit). Split numbers into two groups by that bit. XOR each group → one unique per group. |
| "Can you do it without XOR?" | Yes — use sum formula (for missing number), hash map (for single number), or sorting. But XOR gives O(1) space. Mention the trade-offs. |
| "What about overflow?" | In JavaScript, bitwise operations are 32-bit signed. Use >>> 0 to convert to unsigned. In Java/C++, be aware of integer overflow with left shifts. Mention language-specific behavior. |
| "How would you generate all subsets?" | Two approaches: backtracking (flexible, any n) or bitmask enumeration (faster for n ≤ 20). Bitmask: loop 0 to 2^n - 1, check each bit for inclusion. Show you know both. |
| "What's the time complexity of Brian Kernighan's?" | O(k) where k = number of set bits, not O(32). Each iteration clears exactly one set bit. For a number with 3 set bits, it runs 3 times. Worst case is O(32) = O(1) for 32-bit integers. |
The meta-skill
Bit manipulation problems are about recognizing when a problem's structure maps to bit-level operations. The interviewer isn't testing if you've memorized tricks — they're testing if you understand WHY the tricks work. Always explain the underlying property (XOR cancellation, n & (n-1) clearing lowest bit, bitmask as set representation) before writing code.
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
XOR properties: a ^ a = 0, a ^ 0 = a, commutative & associative. Cancels pairs → finds unique
n & (n-1): Clears the lowest set bit. Power of 2 check: n > 0 && (n & (n-1)) === 0
n & (-n): Isolates the lowest set bit. Useful for splitting in Single Number III
Brian Kernighan: Count set bits: while (n) { n &= (n-1); count++; } — O(k) where k = set bits
Check bit i: (n >> i) & 1 — returns 1 if bit i is set, 0 otherwise
Set bit i: n | (1 << i) — forces bit i to 1
Clear bit i: n & ~(1 << i) — forces bit i to 0
Toggle bit i: n ^ (1 << i) — flips bit i
Bitmask subsets: Loop mask = 0 to (1 << n) - 1. Check bit i: mask & (1 << i). Only for n ≤ 20
Triplet unique: Count set bits at each position mod 3. Non-zero → bit belongs to unique number
Operator precedence: ALWAYS parenthesize: (n & 1) === 0, not n & 1 === 0. Bitwise < comparison in precedence
Mental trigger: "O(1) space" + "unique among duplicates" → XOR. "Power of 2" → n & (n-1). "All subsets" + n ≤ 20 → bitmask
Decision Flowchart
What does the problem ask? ├── Find unique element among duplicates? │ ├── Others appear TWICE → XOR all elements │ ├── Others appear THREE times → bit count mod 3 at each position │ ├── TWO unique elements → XOR all, split by differing bit, XOR groups │ └── Missing number in 0..n → XOR values with indices ├── Count or check bits? │ ├── Count set bits → Brian Kernighan's: n &= (n-1), count++ │ ├── Power of 2 → n > 0 && (n & (n-1)) === 0 │ ├── Power of 4 → power of 2 AND bit at even position │ ├── Hamming distance → countBits(x ^ y) │ └── Counting bits 0..n → DP: dp[i] = dp[i>>1] + (i&1) ├── Transform bits? │ ├── Reverse bits → bit-by-bit: extract lowest, place in result │ ├── Set/clear/toggle bit i → use masks: | (1<<i), & ~(1<<i), ^ (1<<i) │ └── Swap without temp → a ^= b; b ^= a; a ^= b ├── Generate all subsets (n ≤ 20)? │ → Bitmask: loop 0 to 2^n - 1, check each bit └── Not sure? → Start with hash map / math. If O(1) space is required, think bits.
One last thing
Bit manipulation is the most "trick-based" pattern in DSA. But the tricks aren't random — they all follow from a few core properties: XOR cancellation, n & (n-1) clearing the lowest bit, and bitmasks representing sets. Understand these three ideas deeply, solve the 7 problems in Section 07, and you'll handle any bit manipulation question with confidence. When in doubt, trace through the binary representation on paper — bits become intuitive when you can see them.