Last 30 Days
No notifications
Bit manipulation lets you solve certain problems in O(1) time using binary operations directly on bits.
| Operator | Symbol | Example (5 & 3) | Result | |
| AND | & | 101 & 011 | 001 (1) | |
| OR | \ | 101 \ | 011 | 111 (7) |
| XOR | ^ | 101 ^ 011 | 110 (6) | |
| NOT | ~ | ~101 | ...010 | |
| Left Shift | << | 101 << 1 | 1010 (10) | |
| Right Shift | >> | 101 >> 1 | 10 (2) |
n & 1 → 0 = even, 1 = oddn << 1n >> 1n & (n-1) === 0Bit manipulation looks intimidating because of all the symbols (&, |, ^, <<, >>), but the underlying ideas are tiny. Once you can read binary like you read decimal and recognise five or six standard tricks, a whole class of "clever" interview problems becomes routine — and a lot of "normal" code (hashing, file permissions, flags) suddenly makes sense.
Computers store every value as a sequence of bits (0 or 1). 8 bits = 1 byte. Larger types (16-bit, 32-bit, 64-bit) are just longer strings of bits.
Each bit position carries a place value = 2 raised to its index, just like decimal places carry 10â¿:
`
Bit position: 7 6 5 4 3 2 1 0
Place value: 128 64 32 16 8 4 2 1
`
Decoding 0b1101: 8 + 4 + 0 + 1 = 13. Encoding 22: 16 + 4 + 2 = 0b10110. Practice this enough that you do it without thinking — everything else builds on top.
`
1011 (11) 1011 (11) 1011 (11)
& 1101 (13) | 1101 (13) ^ 1101 (13)
------ ------ ------
1001 (9) 1111 (15) 0110 (6)
AND: both 1? OR: at least one 1? XOR: exactly one 1?
`
`
~0101 = 1010 // flip every bit (NOT)
0101 << 1 = 1010 // shift left (multiply by 2)
0101 >> 1 = 0010 // shift right (integer divide by 2)
`
That's it. Every "trick" later is one of these six in disguise.
For a single bit at position i:
| Goal | Expression | |
| Set bit i (turn ON) | n \ | (1 << i) |
| Clear bit i (turn OFF) | n & ~(1 << i) | |
| Toggle bit i (flip) | n ^ (1 << i) | |
| Check bit i (0 or 1) | (n >> i) & 1 | |
| Mask lowest k bits | n & ((1 << k) - 1) |
Memorise these. Most "clever" bit problems are just one of these five applied to a counter.
1. Confusing == and &. if (n & 1) checks "is bit 0 set?" — *not* equality.
2. Operator precedence. n & 1 == 0 is parsed as n & (1 == 0) = n & 0 = 0. Always parenthesise: (n & 1) == 0.
3. Forgetting that << overflows. 1 << 32 in JS wraps to 1 (bitwise ops are 32-bit signed). For larger shifts use BigInt or multiplication.
4. Negative shifts. -8 >> 1 is -4 (arithmetic shift, sign preserved). -8 >>> 1 is a huge positive number (logical shift). Pick deliberately.
5. Treating XOR like AND. a ^ a = 0, not a. The most common trip-up.
6. Counting bits with a % loop. Works but slow; use Brian Kernighan's trick (below).
-5 = 11111011)Negative integers are stored using two's complement:
1. Write the positive number in binary.
2. Flip every bit (~).
3. Add 1.
`
+5 = 00000101
~5 = 11111010 (flip)
-5 = 11111011 (add 1)
`
Why this strange scheme? Because then the same hardware adder works for signed and unsigned, and there's only one zero (no +0 / -0 mess). The top (most significant) bit is the sign: 0 = positive, 1 = negative.
Range for n bits: -2^(n-1) to 2^(n-1) - 1. Useful identity: ~n = -(n + 1).
XOR has algebraic properties no other op has:
| Property | What it means |
a ^ a = 0 | identical things cancel |
a ^ 0 = a | XOR with 0 is identity |
| commutative | a ^ b = b ^ a |
| associative | (a^b)^c = a^(b^c) |
| inverse of itself | a ^ b ^ a = b |
Three classic problems collapse to "just XOR":
a ^= b; b ^= a; a ^= b; — clever but useless in real code; great for showing off.`
n << k ≡ n × 2^k
n >> k ≡ ⌊n / 2^k⌋ (for non-negative n)
`
In tight loops on integers this is occasionally meaningful. In modern compilers the optimiser does this automatically, so prefer readable arithmetic in normal code; reach for shifts only when you specifically need bit-level meaning.
Power of 2 check (only one bit set):
`js
const isPow2 = (n) => n > 0 && (n & (n - 1)) === 0;
`
Brian Kernighan's bit count (loops over set bits, not all 32):
`js
function popcount(n) {
let c = 0;
while (n) { n &= (n - 1); c++; }
return c;
}
`
Lowest set bit: n & -n isolates the rightmost 1. Used inside Fenwick (BIT) trees.
A set of n items has 2â¿ subsets. Represent each subset by an integer where bit i means "item i is in":
`js
function subsets(arr) {
const n = arr.length;
const out = [];
for (let mask = 0; mask < (1 << n); mask++) {
const sub = [];
for (let i = 0; i < n; i++) {
if (mask & (1 << i)) sub.push(arr[i]);
}
out.push(sub);
}
return out;
}
`
For [a, b, c]: 000=[], 001=[a], 010=[b], …, 111=[a,b,c]. Time O(n · 2â¿). The same idea unlocks bitmask DP (e.g., Held–Karp TSP in O(2⿠· n²)).
hash & (cap - 1) replaces a modulo and is markedly faster.777 (octal). Same idea: bits as boolean flags.i & -i.(2 ** 31) << 1 overflows. >>> is the logical shift; everything else is arithmetic.1 << 100 just works. There's no >>>.>>> for unsigned right shift; int is 32-bit, long is 64-bit; shifts are masked mod 32 (or 64).| Pattern | Trigger phrase |
| Subset enumeration | "all subsets of size n ≤ 20" |
| Bitmask DP | "visit all cities", "subset DP", n ≤ 20 |
| XOR cancelling | "every element appears twice except one" |
| Bit DP on digits | "count numbers with property X up to N" |
| Power of 2 / parity | "is this a power of two?", "even or odd" |
| Hashing optimisation | "use n & (size - 1) instead of mod" |
When you see n ≤ 20 and "all subsets / orderings", reach instantly for bitmasks.
1. By hand, write 0–15 in 4-bit binary. Repeat until you can do it in 30 seconds.
2. Implement isPow2(n) and popcount(n) (Brian Kernighan). Verify against Number.prototype.toString(2).
3. Solve Single Number (LC 136): XOR all elements.
4. Generate all subsets of [1, 2, 3] using a bitmask loop. Print the mask in binary alongside each subset; trace why the algorithm hits each.