πŸ”’ Bit Manipulation

A learning sheet β€” treat an integer as an array of bits (a tiny set). Then masks select, XOR toggles, and shifts multiply β€” all in O(1).

🧠 1 · How to think (and invent it)

An integer is just bits indexed from the right. Three mental models cover almost everything: an int is a bitset (each bit = a flag / set member); XOR is a toggle that cancels itself; and a mask (AND with a chosen pattern) selects bits. Master a handful of idioms and these problems become one-liners.

The single most useful property: a ^ a = 0 and a ^ 0 = a, and XOR is order-independent. So XOR-ing a pile of numbers makes every pair vanish β€” whatever's left is the odd one out.

Watch it get invented β€” Single Number

  1. Start brute. Every value appears twice except one. A hash-map count finds it β€” but in O(n) extra space.
  2. The leap. XOR everything. Identical values cancel in pairs (a ^ a = 0), and order doesn't matter, so the duplicates annihilate and only the unique value survives.
  3. Payoff. One pass, O(1) space, no map.
XOR of [4,1,2,1,2] β€” pairs cancel column by column 4 = 1 0 0 1 = 0 0 1 2 = 0 1 0 1 = 0 0 1 2 = 0 1 0 βŠ• = 1 0 0 = 4 the two 1s cancel, the two 2s cancel, only 4 remains
4 ^ 1 ^ 2 ^ 1 ^ 2 = 4 ^ (1^1) ^ (2^2) = 4 ^ 0 ^ 0 = 4.
The two idioms that print money n & (n βˆ’ 1) clears the lowest set bit β†’ test power-of-two (=== 0) and count bits (Kernighan). n & (βˆ’n) isolates the lowest set bit. Recognize these two and a huge fraction of bit problems collapse.

🎯 2 · Attack plan

AskIf yes β†’Pattern
Get/set/clear/toggle a bit, power-of-two, count set bits, flags?the idiom toolkitIdioms (Β§4.1)
Find the element that appears an odd number of times (missing/unique)?XOR self-cancellationXOR tricks (Β§4.2)
Represent a set/state as an int β€” subsets, assignment, small-n DP?a bitmaskBitmask (Β§4.3)
Add / multiply / divide without the operator?sum = XOR, carry = AND-shiftBit arithmetic (Β§4.4)
When bit tricks are the WRONG / risky tool Β· Readability: a clear O(n) hash solution often beats a cryptic bit hack β€” reach for bits when O(1) space or speed actually matters.
Β· JS is 32-bit signed for bitwise ops: numbers β‰₯ 2Β³ΒΉ misbehave; >> sign-extends while >>> zero-fills; use >>> 0 or BigInt for large/unsigned values.
Β· Bitmask DP needs 2ⁿ states β€” only feasible for n ≲ 20–22.

πŸ”Ž 3 Β· Recognition map

Words in the promptReach for
"power of two", "count set bits / hamming weight", "i-th bit", "flags / state compression"Idioms Β§4.1
"appears once / twice / thrice", "single number", "missing number", "find the odd one"XOR Β§4.2
"all subsets", "enumerate combinations", "TSP / assignment", "small n DP", "submask"Bitmask Β§4.3
"sum/add without +", "multiply/divide via shifts", "reverse bits", "divide two integers"Arithmetic Β§4.4

🧩 4 · The four core patterns

Β§4.1 is a vocabulary you memorize; Β§4.2–4.4 apply it. Each: a one-liner, a πŸ”‘ invariant, a template, a 🎀 interview lens.

4.1 Β· The idiom toolkit

β€œMask to select, shift to move, AND-with-(nβˆ’1) to peel the lowest bit.”

n = 1 1 0 0 (12) n βˆ’ 1 = 1 0 1 1 (11) n&(nβˆ’1)= 1 0 0 0 (8) the lowest set bit (the rightmost 1) is cleared β†’ loop this to count bits
Each n &= n βˆ’ 1 removes one set bit β€” so it runs once per set bit, not 32 times.
πŸ”‘ Invariant β€” why n & (nβˆ’1) works Subtracting 1 flips the lowest set bit to 0 and all the zeros below it to 1; ANDing with the original keeps every higher bit and wipes that lowest run β€” netting exactly "remove the lowest set bit." Repeating it touches each set bit once.
const getBit   = (n: number, i: number) => (n >> i) & 1;
const setBit   = (n: number, i: number) => n | (1 << i);
const clearBit = (n: number, i: number) => n & ~(1 << i);
const toggleBit= (n: number, i: number) => n ^ (1 << i);

const isPowerOfTwo = (n: number) => n > 0 && (n & (n - 1)) === 0;   // one set bit
const lowestSetBit = (n: number) => n & (-n);                      // isolate it
const clearLowest  = (n: number) => n & (n - 1);                   // remove it

// Count set bits β€” Brian Kernighan: loops once per 1-bit
function popcount(n: number): number {
  let c = 0;
  while (n) { n &= n - 1; c++; }
  return c;
}
🎀 Interview lens Probed: derive n & (nβˆ’1) and n & βˆ’n on the spot; the JS 32-bit / precedence caveats. Β· Escalation: idioms β†’ "Counting Bits" (DP: dp[i] = dp[i & (iβˆ’1)] + 1) β†’ a Fenwick/BIT tree (whose jumps use n & βˆ’n).

4.2 Β· XOR tricks

β€œXOR everything; pairs cancel, the loner survives.”

πŸ”‘ Invariant β€” why it's safe XOR is commutative and associative with a ^ a = 0, a ^ 0 = a. So any value appearing an even number of times contributes nothing to the running XOR; the accumulated result is exactly the XOR of the odd-count values.
// Single Number β€” every value twice except one
const singleNumber = (nums: number[]) => nums.reduce((a, b) => a ^ b, 0);

// Missing Number in [0..n] β€” XOR the indices and the values together
function missingNumber(nums: number[]): number {
  let x = nums.length;
  for (let i = 0; i < nums.length; i++) x ^= i ^ nums[i];   // every present value cancels
  return x;
}

// Single Number III β€” exactly TWO values appear once, the rest twice
function singleNumberIII(nums: number[]): number[] {
  const xorAll = nums.reduce((a, b) => a ^ b, 0);   // = a ^ b  (the two loners)
  const diff = xorAll & (-xorAll);                   // a bit where a and b differ
  let a = 0;
  for (const x of nums) if (x & diff) a ^= x;        // partition by that bit β†’ isolates a
  return [a, a ^ xorAll];                            // b = (a^b) ^ a
}
🎀 Interview lens Probed: the self-cancel properties and O(1) space; for III, why a differing bit cleanly splits the two loners. Β· Escalation: single β†’ missing β†’ single number III β†’ single number II (every value thrice except one: count bits mod 3).

4.3 Β· Bitmask β€” an int as a set

β€œBit i set = item i chosen; loop 0…2βΏβˆ’1 to walk every subset.”

Setup: for small n, a state (which items are picked / visited) fits in one integer. Enumerate all subsets by counting, test membership with a mask, and compress DP state into the int.

πŸ”‘ Invariant β€” why it's complete The integers 0 … 2βΏβˆ’1 are in bijection with the subsets of an n-element set (bit i = "item i included"). So iterating that range visits every subset exactly once; the submask trick (sub βˆ’ 1) & mask walks exactly the subsets of a given mask.
// All subsets of items[0..n-1]
function subsets<T>(items: T[]): T[][] {
  const n = items.length, out: T[][] = [];
  for (let mask = 0; mask < (1 << n); mask++) {
    const pick: T[] = [];
    for (let i = 0; i < n; i++) if (mask & (1 << i)) pick.push(items[i]);
    out.push(pick);
  }
  return out;
}

// Enumerate every submask of `mask` (for bitmask DP over subsets)
function submasks(mask: number): number[] {
  const out: number[] = [];
  for (let sub = mask; sub > 0; sub = (sub - 1) & mask) out.push(sub);
  out.push(0);
  return out;
}
🎀 Interview lens Probed: the set↔int mapping, subset/submask enumeration, and the n ≲ 20 feasibility limit. Β· Escalation: subsets β†’ bitmask DP over states ("Partition to K Equal Subsets", assignment, the TSP dp[mask][i]).

4.4 Β· Bit arithmetic β€” add without +

β€œSum-without-carry is XOR; the carry is AND shifted left; repeat.”

Setup: binary addition splits into two parts β€” the column sum ignoring carries (a ^ b) and the carries themselves ((a & b) << 1). Fold the carry back in until it's gone.

πŸ”‘ Invariant β€” why it terminates a ^ b is the partial sum and (a & b) << 1 are the bits that must carry one place up. Each iteration moves carries strictly leftward, so after at most word-size rounds the carry becomes 0 and a holds the exact sum.
// Sum of Two Integers without + or -
function getSum(a: number, b: number): number {
  while (b !== 0) {
    const carry = (a & b) << 1;   // where both bits are 1 β†’ carry up
    a = a ^ b;                    // add the columns, ignoring carry
    b = carry;                    // now add the carry in
  }
  return a;
}
stepa (sum so far)b (carry)
start5 = 01013 = 0011
1a^b = 0110 (6)(a&b)<<1 = 0010 (2)
20100 (4)0100 (4)
300001000 (8)
41000 (8) βœ“0 β†’ done
🎀 Interview lens Probed: the sum/carry split and why it terminates; handling negatives via two's complement. Β· Escalation: add β†’ multiply / "Divide Two Integers" via shifts & subtraction β†’ "Reverse Bits".

⏱️ 5 · Complexity

PatternTimeSpaceNote
Idioms / XOR / arithmeticO(1) or O(n)O(1)per-int ops are O(word size) = O(1); a scan adds the n
Count set bits (Kernighan)O(set bits)O(1)≀ word size, often far fewer
Subset enumerationO(2ⁿ · n)O(1)all submasks of all masks: O(3ⁿ)
Bitmask DPO(2ⁿ · n)+O(2ⁿ)only for small n
The O(1) win A bit trick replaces a loop/array over up-to-word-size flags with a single machine instruction β€” that's why "power of two", "count bits", and "single number" go from O(n)-space to O(1)-space.

⚠️ 6 Β· Pitfalls β€” and the test that catches each

BugThe input that exposes it
Operator precedence: & | ^ bind looser than == + .n & 1 === 0 parses as n & (1 === 0) β†’ always falsy. Parenthesize: (n & 1) === 0.
JS bitwise is 32-bit signed.1 << 31 is negative; values β‰₯ 2Β³ΒΉ break. Use >>> 0 or BigInt.
>> (sign-extend) vs >>> (zero-fill).-8 >> 1 = -4 but -8 >>> 1 is a huge positive β€” pick deliberately.
Shift amount β‰₯ word size is undefined / wraps.1 << 32 in JS is 1 (shift is mod 32), not 0.
Bitmask: indexing with i instead of 1 << i.Any subset enumeration β†’ wrong members selected.
Assuming Kernighan loops 32 times.Sparse numbers (one set bit) β†’ it loops once; don't hard-code 32 unless you mean it.
πŸ”— This pattern, elsewhere The bit trie (Maximum XOR, greedy MSB-first) lives in trie.html. Bitmask DP (TSP / subset states) is a DP over compressed state; subset enumeration is the bit-twin of backtracking's subsets. n & βˆ’n is the engine of a Fenwick / Binary Indexed Tree. XOR "find the odd one" is a cousin of the array-as-graph cycle trick (Floyd).

πŸ“Š Coverage β€” pattern β†’ LeetCode (medium & hard)

Own each pattern and these are in range. Bold = Hard tier.

PatternRepresentative LeetCodeTier
Idioms191 Number of 1 Bits Β· 231 Power of Two Β· 338 Counting BitsEasy β†’ Med
XOR tricks136 Single Number Β· 137/260 Single Number II/III Β· 268 Missing NumberEasy β†’ Med
Bitmask78 Subsets Β· 320 Generalized Abbreviation Β· 847 Shortest Path Visiting All NodesMed β†’ Hard
Arithmetic67 Add Binary Β· 371 Sum of Two Integers Β· 29 Divide Two Integers Β· 190 Reverse BitsEasy β†’ Med

βœ… 7 Β· Self-check

Cover the right column (blurred β€” hover/tap to reveal). Say your answer out loud first.

PromptAnswer
What does n & (n βˆ’ 1) do, and what two problems use it?Clears the lowest set bit β†’ power-of-two test (=== 0) and Kernighan's set-bit count.
What does n & (βˆ’n) do?Isolates the lowest set bit (the rightmost 1).
Why does XOR-ing an array find the unique element?a ^ a = 0 and order doesn't matter, so all even-count values cancel; the odd one remains.
In Single Number III, how do you split the two loners?XOR all β†’ a^b; pick any set bit of it (they differ there); partition the array by that bit and XOR each group.
How many subsets does an n-set have, and how do you enumerate them?2ⁿ; iterate mask = 0 … 2βΏβˆ’1, bit i meaning "item i included."
Add without +: what are the sum and carry?sum-without-carry = a ^ b; carry = (a & b) << 1; loop until carry is 0.
Name the two JS gotchas.Bitwise ops are 32-bit signed; and &|^ have lower precedence than ==/+ (parenthesize).

Write these cold (no peeking)

For each: write what each bit means (flag? set member? digit?) as a comment before the code.