Pattern 22 of 24 · Bits · 20 min

Bit Manipulation

XOR cancels pairs, masks test and set flags, and n & (n - 1) drops the lowest set bit.

Spot it:every element appears twice except onecount set bitswithout + or -subsets as bitmasksJavaScript you'll use: Bitwise operators Numbers, Math and integer tricks
Say it in 30 seconds
  • 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 ===.

0 of 0 sections ticked
30-second answer

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

Spot it in the problem0 of 4

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
OperatorExample on 4 bitsTypical use
a & b (AND)1100 & 1010 = 1000test a bit
a | b (OR)1100 | 1010 = 1110set bits
a ^ b (XOR)1100 ^ 1010 = 0110toggle, cancel pairs
1 << i1 << 2 = 0100build 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

bits (8×4)
8421
50101
91001
30011
121100
30011
50101
121100
acc0000
nums (7)
5
9
3
12
3
5
12
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
  1. 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.
  2. acc ^= 5: wherever 5 has a 1 (the 4 and 1 columns), acc's bit flips. 0000 becomes 0101, so acc = 5.
  3. acc ^= 9: wherever 9 has a 1 (the 8 and 1 columns), acc's bit flips. 0101 becomes 1100, so acc = 12.
  4. acc ^= 3: wherever 3 has a 1 (the 2 and 1 columns), acc's bit flips. 1100 becomes 1111, so acc = 15.
  5. acc ^= 12: wherever 12 has a 1 (the 8 and 4 columns), acc's bit flips. 1111 becomes 0011, so acc = 3.
  6. 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.
  7. 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.
  8. 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.
  9. 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.

  1. Write small cases in binary

    3 to 6 inputs in 4-bit binary, aligned by column. Patterns jump out.

  2. Mind the 32-bit rule

    Use >>> on unsigned input and >>> 0 when the answer may reach 2^31.

  3. Add parentheses

    &, | and ^ bind more loosely than ===. Write (n & 1) === 0.

  4. Test the edges

    0, 1, a power of two, all ones, and a negative if allowed.

Interactive

Bit lab

Flip the bits of two 8-bit numbers and watch one operator work on every column, or XOR a list and see pairs cancel.

1248163264128A1010010037B00110110108A ^ B?????????

Result 0 / 8 bitsOne operation: 8 bits at once

Tap any bit of A or B to flip it.

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

bits (3×6)
32168421
n101100
n - 1
n & (n - 1)
bits removed (0)
empty
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
  1. n = 44 is 101100 in binary. Instead of testing all 6 positions, remove one set bit per loop and count the loops.
  2. 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.
  3. 101100 & 101011 = 101000: the lower part cancels, so the 4 bit is cleared and the higher bits stay. count = 1, and n becomes 40.
  4. 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.
  5. 101000 & 100111 = 100000: the lower part cancels, so the 8 bit is cleared and the higher bits stay. count = 2, and n becomes 32.
  6. 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.
  7. 100000 & 011111 = 0: the last set bit (32) is gone. count = 3.
  8. 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

masks (8×3)
cba
0000
1001
2010
3011
4100
5101
6110
7111
items (3)
a
b
c
subset (0)
empty
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
  1. 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.
  2. mask = 0 = 000: no bit is set, so the subset is empty. The empty set counts.
  3. mask = 1 = 001: bit 0 (a) is set, so the subset is [a].
  4. mask = 2 = 010: bit 1 (b) is set, so the subset is [b].
  5. mask = 3 = 011: bit 0 (a) and bit 1 (b) are set, so the subset is [a, b].
  6. mask = 4 = 100: bit 2 (c) is set, so the subset is [c].
  7. mask = 5 = 101: bit 0 (a) and bit 2 (c) are set, so the subset is [a, c].
  8. mask = 6 = 110: bit 1 (b) and bit 2 (c) are set, so the subset is [b, c].
  9. mask = 7 = 111: bit 0 (a) and bit 1 (b) and bit 2 (c) are set, so the subset is [a, b, c].
  10. 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

try-it.js
// 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 0

Complexity

Read more · 42 words
TaskTimeSpace
One bitwise operationO(1)O(1)
XOR over n valuesO(n)O(1)
Popcount with n & (n - 1)at most 32 stepsO(1)
All subsets of n itemsO(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)        // 13

Interview 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 3

Score 2 out of 3 or better to mark this page complete.

1. What does XOR-ing all the values of [a, b, a, c, b] together return?

Practice problems

Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.

Open these in the problem list →

In real systems

This pattern is not only for coding rounds. Here is where it does the work in a design interview.

Next: Two extra topics pull the earlier ideas together: designing classes whose every operation is fast, and grids.