Bit Manipulation
XOR cancels pairs, masks test and set flags, and n & (n - 1) drops the lowest set bit.
- O(n), O(1)XOR over n values: time and space
- O(set bits)Popcount loop: once per set bit, max 32
- 32 bitsJavaScript bitwise operators use signed 32-bit integers
Show what to say
“When every element appears twice except one, or I must count set bits, test a power of two, or list the subsets of a small set, I use bit tricks. XOR cancels pairs, n & (n - 1) drops the lowest set bit, and masks built with 1 << i test, set or clear one bit. Each operator is O(1).”
Trade-offs
- XOR vs per-column counts: XOR cancels pairs; with triples, count 1s in each of 32 columns modulo 3, taking O(32 · n) time.
- Bitmask subsets vs backtracking: counting 0 to 2^n - 1 lists every subset with no recursion, but n must stay small; backtracking handles conditions better.
- Plain numbers vs BigInt: plain operators are fast but 32-bit; BigInt handles 64 bits but is slower and cannot mix with plain numbers.
Bring it up before they ask
JavaScript bitwise operators are signed 32-bit, so use >>> 0 when bit 31 may be set, and add parentheses: (n & 1) === 0, because & binds more loosely than ===.
A number is a row of switches (bits), and one operator flips many at once. Three tricks cover most questions: XOR cancels pairs, n & (n - 1) drops the lowest 1, and masks test one switch.
When to reach for it
Tap the signals you can see in the problem.
Remembertwice except onemissingCount set bitspower of twowithout `+` or `-`subset
The core idea: one operator, every bit at once
Picture a panel of switches. An operator works on the whole row in a single step, no loop.
Read more · 101 words
| Operator | Example on 4 bits | Typical use |
|---|---|---|
a & b (AND) | 1100 & 1010 = 1000 | test a bit |
a | b (OR) | 1100 | 1010 = 1110 | set bits |
a ^ b (XOR) | 1100 ^ 1010 = 0110 | toggle, cancel pairs |
1 << i | 1 << 2 = 0100 | build a mask for bit i |
XOR is the star. x ^ x is 0 and x ^ 0 is x, so XOR a whole list and every pair cancels. Only the unpaired value survives:
JavaScript's 32-bit rule. Bitwise operators work on signed 32-bit integers, so bit 31 is the sign. Use x >>> 0 to read the bits as unsigned, and BigInt for wider values.
XOR cancels pairs
- acc
- 0
Every value appears twice except one. acc starts at 0, all bits off. The grid shows every value in binary; the last row is acc.
function xorAll(nums) {
let acc = 0;
for (const x of nums) {
acc ^= x;
}
return acc;
}All steps as text
- Every value appears twice except one. acc starts at 0, all bits off. The grid shows every value in binary; the last row is acc.
- acc ^= 5: wherever 5 has a 1 (the 4 and 1 columns), acc's bit flips. 0000 becomes 0101, so acc = 5.
- acc ^= 9: wherever 9 has a 1 (the 8 and 1 columns), acc's bit flips. 0101 becomes 1100, so acc = 12.
- acc ^= 3: wherever 3 has a 1 (the 2 and 1 columns), acc's bit flips. 1100 becomes 1111, so acc = 15.
- acc ^= 12: wherever 12 has a 1 (the 8 and 4 columns), acc's bit flips. 1111 becomes 0011, so acc = 3.
- acc ^= 3: wherever 3 has a 1 (the 2 and 1 columns), acc's bit flips. 0011 becomes 0000, so acc = 0. This is the second 3: it flips back exactly the bits the first 3 flipped.
- acc ^= 5: wherever 5 has a 1 (the 4 and 1 columns), acc's bit flips. 0000 becomes 0101, so acc = 5. This is the second 5: it flips back exactly the bits the first 5 flipped.
- acc ^= 12: wherever 12 has a 1 (the 8 and 4 columns), acc's bit flips. 0101 becomes 1001, so acc = 9. This is the second 12: it flips back exactly the bits the first 12 flipped.
- The pairs 5, 3 and 12 cancelled, so acc is the single value: 9. Each bit of acc is the parity of its column: a column with an even number of 1s ends at 0.
Play with it
Tap bits of A and B, pick an operator, and watch the result.
Read more · 81 words
Then switch to Single number and watch each pair cancel.
Bit bugs are silent, so work in this order.
Write small cases in binary
3 to 6 inputs in 4-bit binary, aligned by column. Patterns jump out.
Mind the 32-bit rule
Use
>>>on unsigned input and>>> 0when the answer may reach 2^31.Add parentheses
&,|and^bind more loosely than===. Write(n & 1) === 0.Test the edges
0, 1, a power of two, all ones, and a negative if allowed.
Templates
1. Masks for single bits
1 << i has only bit i set: AND with it to test, OR to set, AND with its complement to clear, XOR to toggle.
reverseBits is Reverse BitsEasy. In Sum of Two IntegersMedium, a ^ b adds without carries and (a & b) << 1 is the carry. Repeat until no carry is left.
// Template 1: masks. 1 << i is a number with only bit i set; combine it with &, | and ^
// to test, set, clear or toggle that one bit and leave every other bit alone.
// Bitwise operators work on 32-bit integers, so i must be between 0 and 31.
export function getBit(n, i) {
return (n >> i) & 1; // 0 or 1
}
export function setBit(n, i) {
return n | (1 << i);
}
export function clearBit(n, i) {
return n & ~(1 << i); // ~ flips the mask: every bit is 1 except bit i
}
export function toggleBit(n, i) {
return n ^ (1 << i);
}
// Walk all 32 bits from the lowest up, building the reversed number. >>> keeps everything unsigned.
export function reverseBits(n) {
let result = 0;
for (let i = 0; i < 32; i++) {
result = (result << 1) | (n & 1);
n >>>= 1;
}
return result >>> 0;
}2. XOR cancellation
xorAll is all of Single NumberEasy. For Missing NumberEasy, also XOR in 0 to n so every present number appears twice.
// Template 2: XOR cancellation. x ^ x = 0, x ^ 0 = x, and the order doesn't matter, so
// XOR-ing a list leaves exactly the bits of the values that appear an odd number of times.
export function xorAll(nums) {
let acc = 0;
for (const x of nums) acc ^= x;
return acc;
}
// Missing number in 0..n: XOR every index and value; present numbers appear twice and cancel.
export function missingNumber(nums) {
let acc = nums.length;
for (let i = 0; i < nums.length; i++) acc ^= i ^ nums[i];
return acc;
}
// Two values appear once, all others twice. a ^ b is non-zero, so pick a bit where a and b differ
// and XOR each half separately: each half then holds exactly one of the two singles.
export function twoSingles(nums) {
const both = xorAll(nums);
const bit = both & -both; // lowest set bit of a ^ b
let a = 0;
for (const x of nums) if (x & bit) a ^= x;
return [a, both ^ a].sort((x, y) => x - y);
}3. Drop the lowest set bit
Subtracting 1 turns the lowest 1 into 0 and the 0s below it into 1s.
Read more · 39 words
AND with n wipes them: n loses its lowest 1, so a loop runs once per set bit. Try your own n with Edit input:
popcount solves Number of 1 BitsEasy. Counting BitsEasy reuses smaller answers: bits[i] = bits[i >> 1] + (i & 1).
n & (n - 1) drops the lowest set bit
- n
- 44
- count
- 0
n = 44 is 101100 in binary. Instead of testing all 6 positions, remove one set bit per loop and count the loops.
function popcount(n) {
let count = 0;
while (n !== 0) {
const below = n - 1;
n = n & below;
count++;
}
return count;
}All steps as text
- n = 44 is 101100 in binary. Instead of testing all 6 positions, remove one set bit per loop and count the loops.
- n - 1 = 43. Subtracting 1 turns the lowest 1 of n (the 4 bit) into 0 and every 0 below it into 1; the higher bits don't change.
- 101100 & 101011 = 101000: the lower part cancels, so the 4 bit is cleared and the higher bits stay. count = 1, and n becomes 40.
- n - 1 = 39. Subtracting 1 turns the lowest 1 of n (the 8 bit) into 0 and every 0 below it into 1; the higher bits don't change.
- 101000 & 100111 = 100000: the lower part cancels, so the 8 bit is cleared and the higher bits stay. count = 2, and n becomes 32.
- n - 1 = 31. Subtracting 1 turns the lowest 1 of n (the 32 bit) into 0 and every 0 below it into 1; the higher bits don't change.
- 100000 & 011111 = 0: the last set bit (32) is gone. count = 3.
- n is 0 after 3 loops, one per set bit. The removed bits 4 + 8 + 32 add back up to 44.
// Template 3: n & (n - 1) removes the lowest set bit. n - 1 flips that bit to 0 and every 0 below
// it to 1, so the AND keeps the higher bits and wipes out the rest.
export function popcount(n) {
let count = 0;
while (n !== 0) {
n &= n - 1; // one iteration per set bit
count++;
}
return count;
}
// A power of two has exactly one set bit, so removing it leaves 0.
export function isPowerOfTwo(n) {
return n > 0 && (n & (n - 1)) === 0;
}
// n & -n keeps only the lowest set bit (-n is ~n + 1 in two's complement).
export function lowestSetBit(n) {
return n & -n;
}
// Counts for every number 0..n: i >> 1 is i without its last bit, already computed.
export function countBitsUpTo(n) {
const bits = new Array(n + 1).fill(0);
for (let i = 1; i <= n; i++) bits[i] = bits[i >> 1] + (i & 1);
return bits;
}4. Subsets as bitmasks
The numbers 0 to 2^n - 1 are all the n-bit patterns.
Read more · 37 words
Each says which of n items are in. Count through them to list every subset, no recursion:
Masks also make compact DP states ("which cities have I visited?" as one number). Keep n at 30 or less.
Subsets as bitmasks
- mask
- 0
- subsets
- 0
3 items, so 8 masks: 0 to 7. Each column of the grid is one item (bit 0 is a, on the right), and each row is one mask.
function subsets(items) {
const out = [];
for (let mask = 0; mask < 1 << items.length; mask++) {
const subset = [];
for (let i = 0; i < items.length; i++) {
if (mask & (1 << i)) subset.push(items[i]);
}
out.push(subset);
}
return out;
}All steps as text
- 3 items, so 8 masks: 0 to 7. Each column of the grid is one item (bit 0 is a, on the right), and each row is one mask.
- mask = 0 = 000: no bit is set, so the subset is empty. The empty set counts.
- mask = 1 = 001: bit 0 (a) is set, so the subset is [a].
- mask = 2 = 010: bit 1 (b) is set, so the subset is [b].
- mask = 3 = 011: bit 0 (a) and bit 1 (b) are set, so the subset is [a, b].
- mask = 4 = 100: bit 2 (c) is set, so the subset is [c].
- mask = 5 = 101: bit 0 (a) and bit 2 (c) are set, so the subset is [a, c].
- mask = 6 = 110: bit 1 (b) and bit 2 (c) are set, so the subset is [b, c].
- mask = 7 = 111: bit 0 (a) and bit 1 (b) and bit 2 (c) are set, so the subset is [a, b, c].
- All 8 masks are used, so every subset appears exactly once: each bit is one include-or-skip choice, and counting runs through every combination of choices.
// Template 4: subsets as bitmasks. Bit i of mask says whether items[i] is in the subset, so
// counting mask from 0 to 2^n - 1 lists every subset exactly once. Keep n at 30 or less:
// 1 << 31 is already negative in JavaScript.
export function subsets(items) {
const n = items.length;
const out = [];
for (let mask = 0; mask < 1 << n; mask++) {
const subset = [];
for (let i = 0; i < n; i++) {
if (mask & (1 << i)) subset.push(items[i]);
}
out.push(subset);
}
return out;
}
// Every submask of mask, from mask itself down to 0. (s - 1) & mask jumps to the next smaller
// number that only uses bits of mask.
export function submasks(mask) {
const out = [];
for (let s = mask; ; s = (s - 1) & mask) {
out.push(s);
if (s === 0) break;
}
return out;
}Try it
// Edit the numbers and press Run. The functions are the templates above.
console.log((44).toString(2), getBit(44, 2), getBit(44, 0)); // 101100 1 0
console.log(setBit(44, 0), clearBit(44, 5)); // 45 12
console.log(xorAll([5, 9, 3, 12, 3, 5, 12])); // 9
console.log(missingNumber([4, 0, 1, 3])); // 2
console.log(twoSingles([1, 2, 1, 3, 2, 5])); // [3, 5]
console.log(popcount(44), isPowerOfTwo(64), lowestSetBit(44)); // 3 true 4
console.log(subsets(['a', 'b', 'c']).length); // 8
// The 32-bit rule: bitwise results are signed 32-bit integers.
console.log(1 << 31, (1 << 31) >>> 0); // -2147483648 2147483648
console.log(reverseBits(1)); // 2147483648
console.log(~5, 7.9 | 0, 2 ** 32 | 0); // -6 7 0Complexity
Read more · 42 words
| Task | Time | Space |
|---|---|---|
| One bitwise operation | O(1) | O(1) |
| XOR over n values | O(n) | O(1) |
Popcount with n & (n - 1) | at most 32 steps | O(1) |
| All subsets of n items | O(n · 2^n) | O(n · 2^n) |
JavaScript you'll use
JavaScript refresher: Bitwise operators Numbers, Math and integer tricks(n >> i) & 1 // bit i of n: 0 or 1
n | (1 << i) // set bit i
n & ~(1 << i) // clear bit i
n & (n - 1) // n without its lowest set bit
n & -n // only the lowest set bit
x >>> 0 // the same 32 bits, read as unsigned
~x // flips every bit: ~5 is -6
(13).toString(2) // "1101": binary for debugging
parseInt("1101", 2) // 13Interview questions
Every number appears three times except one. XOR no longer cancels: what now?
Count the 1s in each of the 32 columns. Triples add a multiple of 3, so count % 3 is the single number's bit.
Two numbers appear once and all others twice. How do you find both?
XOR everything to get a ^ b, and take a set bit of it. Split the array by that bit and XOR each half.
How do you check whether n is a power of two?
n > 0 && (n & (n - 1)) === 0: exactly one set bit.
When would you store a set as a bitmask instead of a Set?
When the universe is small and fixed. Union is |, intersection is &, and the whole set is one number you can use as a memo key.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Practice problems
Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.
- Single NumberEvery number appears twice except one. Find the loner in one pass with O(1) extra space.Easy
- Number of 1 BitsCount how many bits are set in the binary form of a non-negative integer.Easy
- Counting BitsFor every number from 0 to n, count its 1 bits, reusing the answers you have already computed.Easy
- Missing NumberAn array holds n distinct numbers from the range 0 to n. Find the one that is missing.Easy
- Reverse BitsReverse the order of the 32 bits of an unsigned integer.Easy
- Sum of Two IntegersAdd two integers without using the + or - operators.Medium
In real systems
This pattern is not only for coding rounds. Here is where it does the work in a design interview.
- Sharding StrategiesSnowflake IDs pack a timestamp, machine id and sequence number into 64 bits with shifts and masks.
- Caching TechniquesA Bloom filter is a bit array plus a few hash functions: set bits on write, test bits on read.
- Design a Unique ID GeneratorA Snowflake id packs time, worker and sequence into 64 bits with shifts and masks, and decoding reverses them.