Bit Manipulation
Bit manipulation treats integers as arrays of bits and processes them all at once. Learn the operators, the identities interviewers expect (n & (n−1), n & −n, XOR cancellation), the crucial differences between Python, TypeScript and C# integers, and watch XOR, popcount and carry-based addition animated.
🎯 What you'll learn
- AND, OR, XOR, NOT and shifts
- n & (n − 1), n & −n and XOR cancellation
- 32-bit behaviour in TS (>>> 0) vs unbounded Python ints
- Six LeetCode problems with animated walkthroughs
📚 Before you start
Bit manipulation treats integers as arrays of bits and works on all of them at once with single CPU instructions. In interviews it appears in three ways: XOR tricks (find the unique element, swap without a temp), bit counting and masks (popcount, powers of two, flags), and bitmasks as sets (all subsets of n ≤ 20 items, DP over subsets). The operators are few; knowing what each identity does is the whole topic.
1The Operators
| Op | Meaning | Use |
|---|---|---|
a & b | AND | test/clear bits (masks) |
a \| b | OR | set bits |
a ^ b | XOR | toggle; x ^ x = 0, x ^ 0 = x |
~a | NOT | invert (in two's complement ~a = −a − 1) |
a << k | shift left | multiply by 2ᵏ, build masks 1 << k |
a >> k | shift right | divide by 2ᵏ (arithmetic: keeps the sign) |
a >>> k | unsigned shift (JS/TS, Java) | treat as unsigned 32-bit |
2Essential Identities
function bitDemo(n: number): number[] {
let c = 0, x = n;
while (x) { x &= x - 1; c++; } // popcount by dropping lowest bits
return [
n & 1,
(n >> 2) & 1,
n | (1 << 3),
n & ~(1 << 0),
n ^ (1 << 1),
n & (n - 1),
n & -n,
c,
];
}def bit_demo(n):
return [
n & 1, # lowest bit: odd?
(n >> 2) & 1, # bit 2
n | (1 << 3), # set bit 3
n & ~(1 << 0), # clear bit 0
n ^ (1 << 1), # toggle bit 1
n & (n - 1), # drop the lowest set bit
n & -n, # isolate the lowest set bit
bin(n).count("1"), # popcount
]n & (n − 1)drops the lowest set bit: counting bits in O(set bits), andn > 0 and n & (n − 1) == 0tests power of two.n & −nisolates the lowest set bit (used in Fenwick trees).- XOR of a value with itself cancels: XOR everything to find the element that appears once.
(mask >> i) & 1reads bit i; iteratemaskfrom 0 to2ⁿ − 1to enumerate all subsets.
3Language Differences (Important)
| Python | JavaScript / TypeScript | C# | |
|---|---|---|---|
| integer size | unbounded (no overflow) | numbers are doubles; bitwise ops convert to 32-bit signed | int 32-bit, long 64-bit |
| negative numbers | infinite leading 1s; mask with & 0xFFFFFFFF to emulate 32-bit | x >>> 0 converts to unsigned 32-bit | uint/ulong for unsigned |
| popcount | bin(n).count("1"), n.bit_count() (3.10+) | loop or manual | BitOperations.PopCount |
This matters for problems like Reverse Bits and Sum of Two Integers: a JS solution must finish with >>> 0 to return an unsigned value, and Python needs an explicit 32-bit mask.
Under the HoodTwo's Complement▾
Negative integers are stored as two's complement: −x = ~x + 1. That's why n & −n isolates the lowest set bit: −n flips every bit above the lowest 1 and keeps that 1. CPUs implement AND/OR/XOR/shift in one cycle and have dedicated instructions for popcount (POPCNT) and leading/trailing zero counts; databases use bitmaps for fast filtering (bitmap indexes, Roaring bitmaps), and Bloom filters and permission flags are bitsets.
⚠️ Common Pitfalls
- Operator precedence:
n & 1 == 0parses asn & (1 == 0)in many languages. Use parentheses. - JS 32-bit conversion:
2 ** 32 | 0is 0; use>>> 0for unsigned results. - Python negatives:
-1 >> 1is still −1 and loops never end without a mask. - Shifting by ≥ width is undefined or wraps (C#/Java mask the shift count to 5 or 6 bits).
- Using bitmasks for n > ~25: 2ⁿ subsets explode.
📋 Quick Reference
test bit i: (n >> i) & 1 · set: n | (1<<i) · clear: n & ~(1<<i) · toggle: n ^ (1<<i)
drop lowest 1: n & (n-1) · lowest 1: n & -n · power of 2: n > 0 && (n & (n-1)) == 0
XOR: a^a = 0, a^0 = a (find the unique element)
JS unsigned: x >>> 0 · Python 32-bit: x & 0xFFFFFFFFSix problems on XOR cancellation, bit counting, bit DP, unsigned reversal and addition from logic gates.
1Single Number (LeetCode 136)
🟢 Easy. Every element appears twice except one. Find it in O(n) time and O(1) space.
[4,1,2,1,2] → 4Approach. XOR all numbers: pairs cancel (x ^ x = 0) and XOR is commutative and associative, so only the single number remains.
Complexity. O(n) time, O(1) space.
function singleNumber(nums: number[]): number {
return nums.reduce((acc, x) => acc ^ x, 0);
}def single_number(nums):
result = 0
for x in nums:
result ^= x
return result2Number of 1 Bits (LeetCode 191)
🟢 Easy. Return the number of set bits in the binary representation of a non-negative integer.
11 = 1011₂ → 3Approach. Repeatedly drop the lowest set bit with n &= n − 1 and count iterations: the loop runs once per set bit, not once per bit position.
Complexity. O(number of set bits) time.
function hammingWeight(n: number): number {
let count = 0;
n = n >>> 0; // treat as unsigned 32-bit
while (n) { n = (n & (n - 1)) >>> 0; count++; }
return count;
}def hamming_weight(n):
count = 0
while n:
n &= n - 1 # drop the lowest set bit
count += 1
return count3Counting Bits (LeetCode 338)
🟢 Easy. Return an array ans where ans[i] is the number of 1 bits in i, for 0 ≤ i ≤ n, in O(n).
n = 5 → [0,1,1,2,1,2]Approach. DP on bits: i >> 1 drops the last bit, which is i & 1, so bits[i] = bits[i >> 1] + (i & 1).
Complexity. O(n) time.
function countBits(n: number): number[] {
const bits = new Array(n + 1).fill(0);
for (let i = 1; i <= n; i++) bits[i] = bits[i >> 1] + (i & 1);
return bits;
}def count_bits(n):
bits = [0] * (n + 1)
for i in range(1, n + 1):
bits[i] = bits[i >> 1] + (i & 1)
return bits4Missing Number (LeetCode 268)
🟢 Easy. nums contains n distinct numbers from 0..n. Return the missing one.
[3,0,1] → 2Approach. XOR every index 0..n with every value: each present number cancels with its index, leaving the missing one. (The sum formula n(n+1)/2 − sum also works; XOR avoids overflow in fixed-width languages.)
Complexity. O(n) time, O(1) space.
function missingNumber(nums: number[]): number {
let result = nums.length;
nums.forEach((x, i) => { result ^= i ^ x; });
return result;
}def missing_number(nums):
result = len(nums)
for i, x in enumerate(nums):
result ^= i ^ x
return result5Reverse Bits (LeetCode 190)
🟢 Easy. Reverse the bits of a 32-bit unsigned integer.
00000010100101000001111010011100 → 00111001011110000010100101000000Approach. 32 times: shift the result left, append the input's lowest bit, shift the input right. In JavaScript, finish with >>> 0 so the result is read as unsigned; in Python, results stay within 32 bits because you only append 32 bits.
Complexity. O(32) time.
function reverseBits(n: number): number {
let result = 0;
for (let i = 0; i < 32; i++) {
result = ((result << 1) | (n & 1)) >>> 0;
n >>>= 1;
}
return result;
}def reverse_bits(n):
result = 0
for _ in range(32):
result = (result << 1) | (n & 1)
n >>= 1
return result6Sum of Two Integers (LeetCode 371)
🟠 Medium. Return a + b without using + or −.
a = 2, b = 3 → 5Approach. XOR adds without carries; (a & b) << 1 computes the carries. Repeat until there are no carries. In Python, integers are unbounded, so mask to 32 bits on every step and convert back to a signed value at the end; in JS/TS, the | 0 semantics of bitwise ops already work in 32 bits.
Complexity. O(32) iterations.
function getSum(a: number, b: number): number {
while (b !== 0) {
const carry = (a & b) << 1;
a = a ^ b;
b = carry;
}
return a;
}def get_sum(a, b):
MASK, MAX = 0xFFFFFFFF, 0x7FFFFFFF
while b & MASK:
a, b = (a ^ b) & MASK, ((a & b) << 1) & MASK
a &= MASK
return a if a <= MAX else ~(a ^ MASK) # back to signed 32-bit🧠 Key takeaways
n & (n − 1)drops the lowest 1;n & −nisolates it;x ^ x = 0.- Power of two:
n > 0 and n & (n − 1) == 0. - TS bitwise ops are 32-bit signed: use
>>> 0for unsigned; Python ints are unbounded: mask with0xFFFFFFFF. - Bitmasks enumerate subsets for small n (2ⁿ).
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.