Bit Manipulation

Integers are bit vectors. A handful of identities — XOR cancels equal pairs, AND masks and tests, n & (n-1) erases the lowest set bit — collapse problems that look hard into a single tight loop with O(1) extra space and O(32) time.

Topic guide7 problems
The unlock

Stop seeing an integer as a number and start seeing it as a fixed-width row of switches you flip in parallel — one bitwise instruction does to all 32 columns at once what a loop would do one element at a time.

MENTAL MODEL A 32-lane parallel machine, not a number

An intis really a row of 32 light switches. The bitwise operators don't do arithmetic — they flip every switch simultaneously according to a fixed rule. Each operator has exactly one job, and once you assign it that job you stop thinking about magnitude entirely:

  • & AND = keep / test — a column survives only if both inputs are on. Mask with it.
  • | OR = turn on — force columns on.
  • ^ XOR = controlled toggle— a column flips wherever the other input is on, and stays put where it's off.
  • << / >> = slide the whole row left or right.
The reframe → a bitwise op is one O(1) instruction that replaces a whole loop. The question is never “what arithmetic?” — it's “which switches do I want to keep, set, or toggle, all at once?”

XOR = PAIRWISE CANCEL The single most useful identity in the toolkit

Two facts carry half of all bit problems: a ^ a === 0 (anything XORed with itself vanishes) and a ^ 0 === a (XOR with nothing is a no-op). Because XOR is commutative and associative, you can throw a whole list at it in any order — everything that appears an even number of times annihilates, and only the odd-one-out is left standing.

Watch a column of values cancel down to the lone survivor:

nums = [ 4, 1, 2, 1, 2 ]      one number is unpaired.

stack them as bit rows and XOR straight down each column:

    4 :  1 0 0
    1 :  0 0 1
    2 :  0 1 0
    1 :  0 0 1
    2 :  0 1 0
        -------    XOR each column  (1s cancel in pairs)
   xor:  1 0 0  = 4   ◄ the only value without a twin

The 1s, 2s each appear an even number of times → they pair off to 0.
4 has no partner, so its bits are the only ones left standing.
The trick →XOR is a perfect “remove the duplicates for free” accumulator that needs O(1) space and never overflows. Missing Number is the same idea: XOR the indices 0..n against the values so each present number cancels its index, and the gap is what remains.

SEE IT n & (n−1) snips the lowest 1

Subtracting 1 from n flips its lowest set bit to 0 and turns every 0 below it into a 1 (borrow propagation). AND that against the original and only the lowest 1 disappears — every higher bit is untouched:

n        = 0 1 0 1 1 0 0   (88)
n - 1    = 0 1 0 1 0 1 1   ◄ lowest 1 became 0, the 0s under it became 1
           ---------------  AND them together:
n &(n-1) = 0 1 0 1 0 0 0   (80)  ◄ exactly the lowest 1 got snipped

Each AND-with-(n-1) deletes ONE set bit. Loop until n hits 0 and you
have looped once per 1-bit — that count IS the popcount.

So a loop of n &= n - 1 deletes set bits one at a time and runs exactly popcount iterations, not 32. Building a result the other direction — pulling one bit off and shifting it into a bucket — is the same idea in reverse:

reverse the low 4 bits of n = 1 0 1 1

  step  pull n&1   result so far (shift left, OR the bit in)
  ----  --------   ---------------------------------------
   0       1       0 0 0 1
   1       1       0 0 1 1
   2       0       0 1 1 0
   3       1       1 1 0 1   ◄ bits emerged in reverse order

result = (result << 1) | (n & 1);  n >>>= 1;   repeat.
Reading n low→high while writing result high→low reverses it.
Power-of-two falls out free → exactly one bit set means one snip empties n, so n > 0 && (n & (n - 1)) === 0.

HOW TO THINK The cold-start ladder — match the ask to the operator

Faced with a bit problem, don't reach for a 32-iteration loop reflexively. Ask what the problem is really doing to the bits, in this order:

  1. Do pairs cancel / is there an odd one out? XOReverything. Single Number, Missing Number, and “find the duplicate by parity” all collapse to one accumulator line.
  2. Am I testing, setting, or clearing one specific bit k? → build the mask 1 << k. Test with (n >> k) & 1, set with n | (1 << k), clear with n & ~(1 << k).
  3. Am I iterating over the set bits? → the n & (n - 1) loop. Counting Bits, Hamming weight, power-of-two — all popcount in disguise.
  4. Am I building a result bit by bit? → shift-and-OR: result = (result << 1) | (n & 1), then n >>>= 1. Reverse Bits, or any “assemble an integer”.
look at what the problem is really asking about the bits:

    "find the odd one out / pairs cancel?"   -> XOR everything
        result = 0; for n in nums: result ^= n

    "test / set / clear bit k?"              -> mask with (1 << k)
        test  :  (n >> k) & 1
        set   :  n | (1 << k)
        clear :  n & ~(1 << k)

    "walk the 1-bits only?"                  -> n & (n - 1) loop
        while n != 0: n &= n - 1; count++

    "build a number bit by bit?"             -> shift-and-OR
        result = (result << 1) | (n & 1); n >>>= 1
The unlock → almost every bit problem is one of these four shapes wearing a costume. Name the shape and the operator is decided for you.

SAY IT Name the operator’s job out loud

Before writing the loop, state the invariant as a sentence. If you can say what each operator is for, you can't reach for the wrong one:

  • Single Number: “XOR everything; pairs cancel to 0, so the resultholds only the unpaired bits.”
  • Number of 1 Bits: “each n &= n - 1removes exactly one set bit, so the iteration count equals the number of 1s.”
  • Sum of Two Integers:a ^ b is the sum with carries ignored; (a & b) << 1is the carries — loop until there are no carries left.”
Invariant for popcount →nhas strictly fewer set bits after every iteration.” That single fact proves the loop both terminates and counts correctly.

WHEN IT BREAKS JS is 32-bit signed — the sign bit bites

The mental model assumes a clean unsigned row of bits, but JavaScript coerces every bitwise operand to a 32-bit signed integer. The top switch is the sign bit, and two things go wrong because of it:

  • >> is an arithmetic shift — it copies the sign bit inward, so -1 >> 1 stays -1. When you want a clean zero-fill (reverse bits, popcount, building results) use >>> instead.
  • A carry or a high result can land negative. In Sum of Two Integers an unmasked carry loops forever; force it back to the unsigned row with >>> 0each pass, and normalize Reverse Bits' output the same way.
Rule of thumb → the instant you treat the row as unsigned, append >>> 0. If the numbers can exceed 32 bits at all, the whole bit-vector model is off the table — switch to BigInt or plain arithmetic.

MNEMONIC n & (n−1) snips the lowest 1.

n & (n−1) snips the lowest set bit.It's the one bit trick to truly memorize: it turns "count / clear bits" into a loop that runs once per 1-bit, not once per bit. The Visualize tab shows each lowest 1 vanishing.

PATTERN Treat integers as bit vectors

Every integer is a compact array of 0s and 1s. The four bitwise operators each have a single, memorable job:

  • a ^ b (XOR) — bits that differ. Crucially, a ^ a === 0 and a ^ 0 === a, so XOR-ing a sequence cancels every duplicate and leaves the survivor.
  • a & b (AND) — bits that are both set. Use it to test a bit (n & 1 checks parity) or to mask out all but certain bits.
  • a | b (OR) — sets bits. Use it to turn a bit on in a mask or accumulate flags.
  • a << k / a >> k — shifts, equivalent to multiplying or dividing by powers of two.

KEY IDEA The core tricks

XOR identity: a ^ a = 0, a ^ 0 = a. XOR-accumulate a list — every element that appears an even number of times cancels, leaving only the odd-count survivor. Used in Single Number and Missing Number.
Brian Kernighan: n & (n - 1) clears the lowest set bit of n. Loop until n === 0 and you count exactly as many iterations as there are set bits — O(k) instead of O(32).

Power-of-two test falls out for free: n > 0 && (n & (n - 1)) === 0 is true iff exactly one bit is set.

COST O(32) vs O(k) — loop variants

Iterate all 32 bits
O(32)
Right-shift and mask each position. Constant but wasteful for sparse integers.
Brian Kernighan popcount
O(k)
Loop only as many times as there are 1-bits. Much faster when the integer is sparse.

Both are technically O(1) for fixed-width integers, but the Kernighan loop is the expected answer in interviews because it demonstrates you understand the trick.

GOTCHA JS is 32-bit signed — use >>> for unsigned

JavaScript coerces operands to 32-bit signed integers before every bitwise operation and returns a signed 32-bit result. This means:

  • 1 << 31 is -2147483648 (the sign bit), not a large positive number.
  • >> is an arithmetic right-shift — it copies the sign bit. For bit reversal or popcount you almost always want >>> (zero-fill / unsigned).
  • In add-without-plus, the carry can go negative in JS. Write carry >>> 0 to force it back to unsigned and avoid an infinite loop.

Rule of thumb: whenever your result should be treated as an unsigned 32-bit value, append >>> 0 to normalize it.

RUN IT n & (n−1) snips the lowest 1

step 0 / 9
STARTCount the set bits of 156 = 10011100. Naively you'd test all 8 bits; instead, n&(n−1) snips the lowest 1 so we loop once per 1-bit.
bits112806403211618140201
State
156
n
10011100
n (bin)
0
count
lowest set bitset bit (1)
slowfast

TRIGGERS When you see ___ → reach for ___

Reach for bit manipulation when the problem is fundamentally about individual binary digits of integers — detecting, counting, toggling, or exploiting the cancellation property of XOR. The problems are almost always O(1) space and O(n) or O(32) time.

"every element appears twice except one" (or k times except one)XOR-accumulate all values — pairs cancel, the singleton survives
"count set bits" / "Hamming weight" / "number of 1-bits"n &= (n-1) loop (Brian Kernighan) — O(k) iterations
"add / sum two integers without using + or -"XOR for digit sum + (AND << 1) for carry, loop until carry is 0
"find the missing number in range 0..n"XOR all indices and all values — only the missing index survives
"reverse the bits of a 32-bit unsigned integer"shift result left, OR in low bit of n, repeat 32 times; use >>> to avoid sign extension
"is the number a power of two?"n > 0 && (n & (n-1)) === 0 — a power of two has exactly one set bit
"enumerate all subsets" / "bitmask DP"iterate 0..(1<<n)-1; bit k set ↔ element k included in the subset

RED FLAGSWhen it's NOT this pattern

  • The numbers exceed 32 bits. JS bitwise ops truncate to 32-bit signed. If the problem involves very large integers (e.g. BigInt range), bit tricks produce wrong results — use BigInt or regular arithmetic instead.
  • The problem isn't really about binary structure.If "bit manipulation" just means "use modulo for odd/even" or "divide by two", the interviewer likely wants the readable arithmetic form.
  • Readability clearly matters more than the micro-trick. In production code review, n % 2 === 0 is preferred over (n & 1) === 0. Use bit tricks in interviews, but flag the tradeoff.
  • Negative number semantics are load-bearing. >> sign-extends in JS, which can corrupt a reverse-bits or popcount result. Audit every shift — if you need zero-fill, switch to >>>.

TEMPLATE XOR accumulate (single / missing)

When → Every element appears an even number of times except exactly one. XOR all values — every duplicate pair annihilates, the lone survivor remains. For missing number, also XOR the complete index set 0..n.

xor-accumulate-single-missing-.ts
// Single number / missing number via XOR accumulation
function singleNumber(nums: number[]): number {
  let result = 0;
  for (const n of nums) {
    result ^= n;          // a ^ a === 0, so every duplicate pair cancels
  }
  return result;          // only the unpaired value survives
}

// Missing number 0..n: XOR all indices AND all values — the missing index survives
function missingNumber(nums: number[]): number {
  let xor = nums.length;  // start with n (the "extra" index not present in nums)
  for (let i = 0; i < nums.length; i++) {
    xor ^= i ^ nums[i];   // each present value cancels its matching index
  }
  return xor;
}
Why it works → XOR is commutative and associative, so order is irrelevant. Each pair contributes x ^ x = 0; the unpaired element contributes x ^ 0 = x.

TEMPLATE Popcount via n & (n-1)

When → You need the number of set bits in an integer (Hamming weight), or you want to check whether an integer is a power of two. Also the basis of the DP recurrence for Counting Bits.

popcount-via-n-n-1-.ts
// Count set bits using Brian Kernighan's trick: n & (n-1) clears the lowest set bit
function hammingWeight(n: number): number {
  let count = 0;
  while (n !== 0) {
    n &= n - 1;           // drops the rightmost 1-bit each iteration
    count++;
  }
  return count;            // O(k) where k = number of set bits, not O(32)
}

// DP approach for Counting Bits (all values 0..n):
// bits[i] = bits[i >> 1] + (i & 1)
function countBits(n: number): number[] {
  const dp = new Array(n + 1).fill(0);
  for (let i = 1; i <= n; i++) {
    dp[i] = dp[i >> 1] + (i & 1);  // right-shift reuses a solved sub-problem
  }
  return dp;
}
DP insight → dp[i] = dp[i >> 1] + (i & 1). Shifting right by one reuses a previously computed sub-problem; the last bit is the only new contribution.

TEMPLATE Add without +

When → The problem forbids + or -. XOR gives the digit-wise sum without carry; AND shifted left gives the carry bits. Repeat until the carry is zero.

add-without-.ts
// Add without + operator: XOR gives sum-without-carry; AND<<1 gives the carry
function getSum(a: number, b: number): number {
  while (b !== 0) {
    const carry = (a & b) << 1;   // compute carry bits (need to shift left by 1)
    a = a ^ b;                    // add without carry
    b = (carry) >>> 0;            // mask to 32 bits — CRITICAL to avoid infinite loop
    // In JS a negative carry stays negative; >>> 0 forces it unsigned so loop terminates
  }
  return a;
}
JS pitfall → The carry can become a negative 32-bit value in JS, causing an infinite loop. Always apply >>> 0 to force it unsigned each iteration.

TEMPLATE Reverse / iterate 32 bits

When → You must process every one of the 32 bit-positions — reversing bits, building a result bit-by-bit, or checking each position with a mask. Use >>> for the input shift to avoid sign extension.

reverse-iterate-32-bits.ts
// Reverse all 32 bits — iterate, shift result left, OR in the low bit of n
function reverseBits(n: number): number {
  let result = 0;
  for (let i = 0; i < 32; i++) {
    result = (result << 1) | (n & 1);   // grab the low bit of n, append to result
    n >>>= 1;                           // unsigned right-shift to avoid sign extension
  }
  return result >>> 0;                  // force unsigned 32-bit for correct output
}

// Power of two check — true iff exactly one bit is set
function isPowerOfTwo(n: number): boolean {
  return n > 0 && (n & (n - 1)) === 0; // n & (n-1) clears the single set bit → 0
}
Remember to normalize the output → result >>> 0 converts the JS signed integer back to an unsigned 32-bit representation, matching what LeetCode expects for Reverse Bits.

PITFALL JS 32-bit signed overflow and needing >>> 0

All JS bitwise ops silently truncate to 32-bit signed integers. A large positive number like 1 << 31 becomes -2147483648. Wherever your algorithm treats the result as unsigned — especially in Reverse Bits and any carry computation — append >>> 0 to reinterpret it as unsigned.

PITFALL Arithmetic >> vs unsigned >>> in right-shifts

>> copies the sign bit (arithmetic shift), so -1 >> 1 is still -1. >>> zero-fills from the left. For popcount loops, bit-reversal, and any situation where you want a logical shift, always use >>> or ensure your input is non-negative.

PITFALL Infinite loop in add-without-plus when not masking the carry

If (a & b) << 1 produces a negative JS integer, the loop condition b !== 0 can cycle forever because the carry never reaches zero through signed arithmetic. Fix: assign b = (carry) >>> 0 so the value is treated as an unsigned 32-bit integer on every iteration.

PITFALL Forgetting that n & (n-1) is undefined-ish for n = 0

The Kernighan loop guard should be while (n !== 0), not while (n), to handle the case n = 0 correctly in TypeScript strict mode (n is a number, not a boolean). Also note that the power-of-two check must start with n > 0 — zero satisfies (n & (n - 1)) === 0 but is not a power of two.