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).
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.
O(n) extra space.a ^ a = 0), and order doesn't matter, so the duplicates annihilate and only the unique value survives.O(1) space, no map.4 ^ 1 ^ 2 ^ 1 ^ 2 = 4 ^ (1^1) ^ (2^2) = 4 ^ 0 ^ 0 = 4.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.| Ask | If yes β | Pattern |
|---|---|---|
| Get/set/clear/toggle a bit, power-of-two, count set bits, flags? | the idiom toolkit | Idioms (Β§4.1) |
| Find the element that appears an odd number of times (missing/unique)? | XOR self-cancellation | XOR tricks (Β§4.2) |
Represent a set/state as an int β subsets, assignment, small-n DP? | a bitmask | Bitmask (Β§4.3) |
| Add / multiply / divide without the operator? | sum = XOR, carry = AND-shift | Bit arithmetic (Β§4.4) |
O(n) hash solution often beats a cryptic bit hack β reach for bits when O(1) space or speed actually matters.>> sign-extends while >>> zero-fills; use >>> 0 or BigInt for large/unsigned values.2βΏ states β only feasible for n β² 20β22.| Words in the prompt | Reach 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.1 is a vocabulary you memorize; Β§4.2β4.4 apply it. Each: a one-liner, a π invariant, a template, a π€ interview lens.
βMask to select, shift to move, AND-with-(nβ1) to peel the lowest bit.β
n &= n β 1 removes one set bit β so it runs once per set bit, not 32 times.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;
}
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).βXOR everything; pairs cancel, the loner survives.β
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
}
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).β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.
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;
}
n β² 20 feasibility limit. Β· Escalation: subsets β bitmask DP over states ("Partition to K Equal Subsets", assignment, the TSP dp[mask][i]).+β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.
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;
}
| step | a (sum so far) | b (carry) |
|---|---|---|
| start | 5 = 0101 | 3 = 0011 |
| 1 | a^b = 0110 (6) | (a&b)<<1 = 0010 (2) |
| 2 | 0100 (4) | 0100 (4) |
| 3 | 0000 | 1000 (8) |
| 4 | 1000 (8) β | 0 β done |
| Pattern | Time | Space | Note |
|---|---|---|---|
| Idioms / XOR / arithmetic | O(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 enumeration | O(2βΏ Β· n) | O(1) | all submasks of all masks: O(3βΏ) |
| Bitmask DP | O(2βΏ Β· n)+ | O(2βΏ) | only for small n |
O(n)-space to O(1)-space.| Bug | The 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. |
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).Own each pattern and these are in range. Bold = Hard tier.
| Pattern | Representative LeetCode | Tier |
|---|---|---|
| Idioms | 191 Number of 1 Bits Β· 231 Power of Two Β· 338 Counting Bits | Easy β Med |
| XOR tricks | 136 Single Number Β· 137/260 Single Number II/III Β· 268 Missing Number | Easy β Med |
| Bitmask | 78 Subsets Β· 320 Generalized Abbreviation Β· 847 Shortest Path Visiting All Nodes | Med β Hard |
| Arithmetic | 67 Add Binary Β· 371 Sum of Two Integers Β· 29 Divide Two Integers Β· 190 Reverse Bits | Easy β Med |
Cover the right column (blurred β hover/tap to reveal). Say your answer out loud first.
| Prompt | Answer |
|---|---|
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). |
isPowerOfTwo, popcount (Kernighan), get/set/clear/togglesingleNumber + singleNumberIIIsubsets + submask enumerationgetSum (add without +)For each: write what each bit means (flag? set member? digit?) as a comment before the code.