Bitwise operators work on individual bits of integers:
// AND (&) — both bits must be 1
5 & 3 // 101 & 011 = 001 = 1
// OR (|) — at least one bit must be 1
5 | 3 // 101 | 011 = 111 = 7
// XOR (^) — bits must differ
5 ^ 3 // 101 ^ 011 = 110 = 6
// Key XOR properties:
// a ^ a = 0, a ^ 0 = a, a ^ b ^ a = b
AND masks bits, OR sets bits, XOR toggles bits and detects differences.
Left shift (<<) multiplies by 2<sup>n</sup>. Right shift (>>) divides by 2<sup>n</sup> (integer division).
// Left shift: a << n = a * 2^n
5 << 1 // 101 -> 1010 = 10
5 << 2 // 101 -> 10100 = 20
// Right shift: a >> n = Math.floor(a / 2^n)
20 >> 1 // 10100 -> 1010 = 10
20 >> 2 // 10100 -> 101 = 5
// Common use: mid = (lo + hi) >> 1 (same as Math.floor((lo+hi)/2))
Shifts are faster than multiplication/division and commonly used in low-level optimization.
A power of 2 has exactly one bit set. The trick: n & (n - 1) clears the lowest set bit. If the result is 0, it was a power of 2.
function isPowerOfTwo(n) {
return n > 0 && (n & (n - 1)) === 0;
}
// 8 = 1000, 7 = 0111 => 1000 & 0111 = 0 ✓
// 6 = 0110, 5 = 0101 => 0110 & 0101 = 4 ✗
isPowerOfTwo(16) // true (10000)
isPowerOfTwo(18) // false (10010)
Time: O(1) | Space: O(1)
Count how many bits are 1 in a number. Use n & (n - 1) to clear the lowest set bit each iteration.
function countSetBits(n) {
let count = 0;
while (n) {
n &= (n - 1); // clear lowest set bit
count++;
}
return count;
}
// 13 = 1101 => 3 set bits
// 7 = 0111 => 3 set bits
// 16 = 10000 => 1 set bit
countSetBits(13) // 3
Time: O(number of set bits) | Space: O(1)
In an array where every element appears twice except one, XOR all elements. Pairs cancel out (a ^ a = 0), leaving the single number.
function singleNumber(nums) {
let result = 0;
for (const n of nums) {
result ^= n;
}
return result;
}
// singleNumber([4, 1, 2, 1, 2]) => 4
// 4^1^2^1^2 = 4^(1^1)^(2^2) = 4^0^0 = 4
Time: O(n) | Space: O(1) — no extra data structure needed.
Use XOR to swap: a ^= b; b ^= a; a ^= b;. This works because XOR is its own inverse.
function swap(a, b) {
a = a ^ b; // a now holds a^b
b = a ^ b; // b = (a^b)^b = a
a = a ^ b; // a = (a^b)^a = b
return [a, b];
}
// swap(5, 3) => [3, 5]
// Step by step: a=5(101), b=3(011)
// a = 101^011 = 110 (6)
// b = 110^011 = 101 (5) = original a
// a = 110^101 = 011 (3) = original b
Note: In practice, a temporary variable is simpler and preferred. XOR swap fails if a and b are the same reference.
Use XOR with a mask that has only the nth bit set: n ^ (1 << pos).
// Toggle bit at position pos
function toggleBit(n, pos) {
return n ^ (1 << pos);
}
// Set bit: n | (1 << pos)
// Clear bit: n & ~(1 << pos)
// Check bit: (n >> pos) & 1
toggleBit(10, 0) // 1010 ^ 0001 = 1011 (11)
toggleBit(11, 0) // 1011 ^ 0001 = 1010 (10)
toggleBit(10, 2) // 1010 ^ 0100 = 1110 (14)
XOR flips the targeted bit: 0 becomes 1 and 1 becomes 0.
XOR all elements to get xor = a ^ b. Find a set bit (the two differ here). Partition elements by that bit to separate a and b.
function findTwoNonRepeating(nums) {
let xor = 0;
for (const n of nums) xor ^= n;
const setBit = xor & (-xor); // lowest set bit
let a = 0, b = 0;
for (const n of nums) {
if (n & setBit) a ^= n;
else b ^= n;
}
return [a, b];
}
// findTwoNonRepeating([1, 2, 3, 1, 2, 5])
// => [3, 5]
Time: O(n) | Space: O(1)
Process each bit from right to left, building the reversed number by shifting and adding bits.
function reverseBits(n) {
let result = 0;
for (let i = 0; i < 32; i++) {
result = (result << 1) | (n & 1);
n >>>= 1;
}
return result >>> 0; // unsigned
}
// reverseBits(0b00000000000000000000000000001011)
// => 0b11010000000000000000000000000000
// = 3489660928
Time: O(32) = O(1) | Space: O(1)
For n elements, iterate from 0 to 2<sup>n</sup>-1. Each number represents a subset: bit j set means include element j.
function subsets(arr) {
const n = arr.length;
const result = [];
for (let mask = 0; mask < (1 << n); mask++) {
const subset = [];
for (let j = 0; j < n; j++) {
if (mask & (1 << j)) subset.push(arr[j]);
}
result.push(subset);
}
return result;
}
// subsets([1, 2, 3])
// => [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]
Total subsets: 2<sup>n</sup> | Time: O(n × 2<sup>n</sup>)
Use n & (n-1) to clear the lowest set bit repeatedly and count iterations. Alternative: use Brian Kernighan's algorithm.
function hammingWeight(n) {
let count = 0;
while (n) {
n &= (n - 1); // clears the lowest set bit
count++;
}
return count;
}
// Input: n=11 (1011 in binary) => Output: 3 (three 1-bits)
// Input: n=128 (10000000) => Output: 1
// Input: n=4294967293 (0b11111111111111111111111111111101) => Output: 31
// Using bit shift:
function countBits(n) {
let count = 0;
while (n) { count += n & 1; n >>>= 1; }
return count;
}
Time: O(k) where k is the number of set bits (= O(log n) worst case) | Space: O(1)
XOR all numbers 0..n AND all array elements. Paired numbers cancel out; the unpaired one is the missing number.
function missingNumber(nums) {
let xor = nums.length; // start with n
for (let i = 0; i < nums.length; i++) {
xor ^= i ^ nums[i];
}
return xor;
}
// Input: [3,0,1] => Output: 2
// Input: [0,1] => Output: 2
// Input: [9,6,4,2,3,5,7,0,1] => Output: 8
Time: O(n) | Space: O(1)
Alternative: sum formula: n*(n+1)/2 - sum(nums). XOR avoids potential overflow.
XOR all elements to get x = a^b. Find any set bit in x (a and b differ there). Split array into two groups by that bit; XOR each group to isolate a and b.
function twoSingleNumbers(nums) {
let xor = 0;
for (const n of nums) xor ^= n; // xor = a ^ b
// Find rightmost set bit (where a and b differ)
const setBit = xor & (-xor); // isolates lowest set bit
let a = 0, b = 0;
for (const n of nums) {
if (n & setBit) a ^= n; // group with this bit set
else b ^= n; // group without this bit
}
return [a, b];
}
// Input: [1,2,3,4,1,2] => Output: [3,4]
// Input: [2,3,7,9,11,2,3,11] => Output: [7,9]
Time: O(n) | Space: O(1)
Use a mask. Shift left into result's MSB while shifting right through input's bits. Process all 32 bit positions.
function reverseBits(n) {
let result = 0;
for (let i = 0; i < 32; i++) {
result = (result << 1) | (n & 1); // add LSB of n to MSB of result
n >>>= 1; // unsigned right shift
}
return result >>> 0; // ensure unsigned 32-bit
}
// Input: 43261596 (binary: 00000010100101000001111010011100)
// Output: 964176192 (binary: 00111001011110000010100101000000)
// Input: 4294967293 (binary: 11111111111111111111111111111101)
// Output: 3221225471 (binary: 10111111111111111111111111111111)
Time: O(32) = O(1) | Space: O(1)
For an array of n elements, there are 2ⁿ subsets. Use a bitmask from 0 to 2ⁿ-1; each bit represents whether to include the corresponding element.
function subsets(nums) {
const n = nums.length;
const result = [];
for (let mask = 0; mask < (1 << n); mask++) {
const subset = [];
for (let i = 0; i < n; i++) {
if (mask & (1 << i)) subset.push(nums[i]);
}
result.push(subset);
}
return result;
}
// Input: [1,2,3] => Output: 8 subsets (2^3)
// mask=0 (000) => [], mask=1 (001) => [1], mask=7 (111) => [1,2,3]
// Input: [1,2] => Output: [[],[1],[2],[1,2]]
Time: O(2ⁿ × n) | Space: O(n) per subset
XOR all elements — duplicates cancel out, leaving the single element. For a sorted array, you can also use binary search: at the single element's position, the pair ordering breaks.
// XOR approach (O(n)):
function singleNonDuplicate(nums) {
return nums.reduce((xor, n) => xor ^ n, 0);
}
// Binary search on sorted array (O(log n)):
function singleNonDuplicateBSearch(nums) {
let lo = 0, hi = nums.length - 1;
while (lo < hi) {
let mid = Math.floor((lo + hi) / 2);
if (mid % 2 === 1) mid--; // ensure mid is even
if (nums[mid] === nums[mid + 1]) lo = mid + 2; // single is on right
else hi = mid; // single is here or left
}
return nums[lo];
}
// Input: [1,1,2,3,3,4,4,8,8] => Output: 2
// Input: [3,3,7,7,10,11,11] => Output: 10
Time: O(log n) binary search | Space: O(1)
Use the XOR swap trick: a = a^b, b = a^b, a = a^b. Works because XOR is its own inverse. Note: fails if a and b are the same variable/reference.
function xorSwap(a, b) {
a = a ^ b;
b = a ^ b; // b = (a^b)^b = a
a = a ^ b; // a = (a^b)^a = b
return [a, b];
}
// Input: a=5, b=3 => Output: a=3, b=5
// Trace: a=5^3=6, b=6^3=5, a=6^5=3 ✓
// Array version (in-place):
function swapInArray(arr, i, j) {
arr[i] ^= arr[j];
arr[j] ^= arr[i];
arr[i] ^= arr[j];
}
Time: O(1) | Space: O(1)
There's a pattern based on n % 4: the XOR of 1..n cycles through 4 states. Use this O(1) formula instead of iterating.
function xorUpToN(n) {
switch (n % 4) {
case 0: return n; // 4,8,12... => n itself
case 1: return 1; // 1,5,9... => 1
case 2: return n + 1; // 2,6,10... => n+1
case 3: return 0; // 3,7,11... => 0
}
}
// xorRange(l, r) = xorUpToN(r) ^ xorUpToN(l-1)
function xorRange(l, r) { return xorUpToN(r) ^ xorUpToN(l - 1); }
// Input: n=4 => Output: 4 (1^2^3^4=4)
// Input: n=5 => Output: 1 (1^2^3^4^5=1)
// Input: n=6 => Output: 7 (1^2^3^4^5^6=7)
// xorRange(3, 7) = xorUpToN(7)^xorUpToN(2) = 0 ^ 3 = 3
Time: O(1) | Space: O(1)
The sign bit is the MSB (bit 31 in 32-bit integers). XOR the two numbers: if the result's MSB is 1, they have opposite signs.
function oppositeSigns(a, b) {
return (a ^ b) < 0; // MSB is 1 means different signs
}
// Input: a=4, b=-7 => Output: true
// Input: a=2, b=4 => Output: false
// Input: a=-2, b=-4 => Output: false
// Determine sign of a number:
function isNegative(n) { return (n >>> 31) === 1; }
// Input: -5 => true, 5 => false, 0 => false
Time: O(1) | Space: O(1)
Use n & (-n) to isolate the lowest set bit (returns a power of 2). Take log₂ to find the bit position.
function rightmostSetBit(n) {
return Math.log2(n & (-n)); // position (0-indexed)
}
// Alternatively, count trailing zeros:
function countTrailingZeros(n) {
if (n === 0) return 32;
let pos = 0;
while ((n & 1) === 0) { n >>>= 1; pos++; }
return pos;
}
// Input: n=12 (1100) => Output: position 2 (bit at index 2 is set)
// Input: n=18 (10010) => Output: position 1 (bit at index 1 is set)
// Input: n=64 (1000000) => Output: position 6
Time: O(log n) for trailing zeros | Space: O(1)
Use DP with the relation: dp[i] = dp[i >> 1] + (i & 1). The count for i equals the count for i/2 (right-shifted) plus the last bit.
function countBits(n) {
const dp = new Array(n + 1).fill(0);
for (let i = 1; i <= n; i++) {
dp[i] = dp[i >> 1] + (i & 1);
}
return dp;
}
// Input: n=5 => Output: [0,1,1,2,1,2]
// 0=0, 1=1, 2=1, 3=2, 4=1, 5=2 (count of 1-bits)
// Input: n=2 => Output: [0,1,1]
// Input: n=0 => Output: [0]
Time: O(n) | Space: O(n)
XOR gives sum without carry. AND << 1 gives carry. Repeat until no carry remains.
function add(a, b) {
while (b !== 0) {
const carry = (a & b) << 1; // carry bits
a = a ^ b; // sum without carry
b = carry;
}
return a;
}
// Input: a=1, b=1 => Output: 2
// Input: a=15, b=25 => Output: 40
// Input: a=7, b=8 => Output: 15
// Trace for a=3(011), b=5(101):
// sum=110=6, carry=001<<1=010=2
// sum=100=4, carry=010=2 (shifted)...
Time: O(log(max(a,b))) iterations | Space: O(1)
A power of 4 must be: (1) a power of 2 (only one set bit), AND (2) the set bit is at an even position (0, 2, 4...). The mask 0x55555555 has 1s only at even positions.
function isPowerOfFour(n) {
return n > 0
&& (n & (n - 1)) === 0 // power of 2: exactly one set bit
&& (n & 0x55555555) !== 0; // set bit is at even position
}
// Input: 1 => Output: true (4^0)
// Input: 16 => Output: true (4^2)
// Input: 5 => Output: false (not a power of 4)
// Input: 2 => Output: false (power of 2 but not 4)
// 0x55555555 = 01010101...01010101 (even bit positions are 1)
Time: O(1) | Space: O(1)