Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

34 chapters
⌕
Beginner10›
B1Complexity analysisB2Arrays & stringsB3HashingB4Two pointersB5Sliding windowB6Binary searchB7Sorting algorithmsB8Stacks & queuesB9Linked listsB10Basic recursion
Intermediate12›
I1TreesI2Tree problems in depthI3Heaps & priority queuesI4Graphs: representationI5Graph problemsI6BacktrackingI7DP: 1DI8DP: 2DI9Greedy algorithmsI10IntervalsI11Bit manipulationI12Matrix problems
Advanced12›
A1Advanced DPA2Union-FindA3Advanced graph algorithmsA4Minimum Spanning TreeA5TriesA6Segment & Fenwick treesA7String algorithmsA8Monotonic stack & queueA9Design problemsA10Advanced backtrackingA11Topological patternsA12Interview strategy
/ search[ ] chaptert top

DSA in JS levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
I11

Bit manipulation

A small, fixed toolkit of tricks that turn up constantly once you recognize them.

The operators, and what they actually do

6 = 110 3 = 011 AND (&): 010 = 2 — 1 only where BOTH have a 1 OR (|): 111 = 7 — 1 where EITHER has a 1 XOR (^): 101 = 5 — 1 where they DIFFER NOT (~): flips every bit — ~6 = -7 (two's complement)
Same two operands, four completely different results depending on the operator.
OperatorSymbolCommon use
AND&check/clear specific bits
OR|set specific bits
XOR^toggle bits, find "the one that's different"
NOT~flip every bit (rarely used alone)
Left shift<<multiply by 2 per shift — x << 1 === x * 2
Right shift>>divide by 2 per shift (rounds toward -∞)
⚠ JS bitwise ops force numbers to 32-bit signed integers 2 ** 32 | 0 is 0, not 4294967296 — every bitwise operator first converts its operands to a 32-bit signed int. This is exactly why x | 0 is a common (if now old-fashioned) "truncate toward zero" trick, and why bit tricks silently break on numbers bigger than 32 bits.

Trick 1 — XOR cancels itself out

x ^ x === 0 and x ^ 0 === x, for any x. XOR-ing a whole list together makes every value that appears an even number of times vanish, leaving only what's left over.

// find the single number that doesn't appear exactly twice — O(n) time, O(1) space
function singleNumber(nums) {
  let result = 0;
  for (const num of nums) result ^= num; // every pair cancels to 0
  return result; // whatever's left is the unpaired one
}

This is a genuinely elegant O(1)-space answer to a problem that looks like it needs a hash set (O(n) space) — worth recognizing "appears an even number of times except one" as an XOR tell.

Trick 2 — check, set, and clear a specific bit

function getBit(num, i)   { return (num >> i) & 1; }        // is bit i a 1?
function setBit(num, i)   { return num | (1 << i); }         // force bit i to 1
function clearBit(num, i) { return num & ~(1 << i); }         // force bit i to 0
function toggleBit(num, i){ return num ^ (1 << i); }          // flip bit i

1 << i builds a number that's all zeros except a single 1 at position i — every one of these four operations is just combining that "mask" with the original number using the right bitwise operator.

Trick 3 — the lowest set bit, and counting set bits

// n & (n - 1) clears the LOWEST set bit — used constantly
function countSetBits(n) {
  let count = 0;
  while (n !== 0) {
    n = n & (n - 1); // each iteration removes exactly one 1-bit
    count++;
  }
  return count; // loop runs once per set bit, not once per bit position — faster than checking all 32
}

// is n a power of 2? a power of 2 has EXACTLY one set bit
function isPowerOfTwo(n) {
  return n > 0 && (n & (n - 1)) === 0;
}
n = 0110 1100 n - 1 = 0110 1011 n & (n-1) = 0110 1000 — lowest 1-bit gone
Subtracting 1 flips every trailing 0 to 1 and the lowest 1 to 0 — ANDing with the original clears just that bit.

Trick 4 — bitmasks as a compact set

For a small, fixed universe of items (say, ≤ 20-30 elements), an integer can represent an entire subset — bit i set means "item i is in the set." This is the foundation of bitmask DP (advanced tier): a whole subset becomes a single number you can use as a state or a Map key, instead of an array you'd need to compare element-by-element.

let mask = 0;
mask |= (1 << 3);        // add item 3 to the set
const has3 = (mask & (1 << 3)) !== 0; // is item 3 in the set?
mask &= ~(1 << 3);        // remove item 3
Say it like this → "Since every value except one appears an even number of times, XOR-ing the whole array together cancels every paired value to zero and leaves exactly the unpaired one — O(n) time, O(1) space, no hash set needed."

Recognizing it in an unseen problem

  • "Without using extra space," combined with numbers that appear in pairs → XOR
  • "Count sallow bits," "power of two," "single bit differs" → the set-bit tricks above
  • A small fixed number of items/states (≤ ~20) where you need to represent "which subset" compactly → bitmask
  • Multiplying/dividing by exact powers of 2 in a performance-sensitive inner loop → shifts, though modern engines often optimize this automatically
Practice this layer

Opens in the editor — write it, run it, and check it against real tests.

Single Number4 tests · beginnerSingle Number II5 tests · advancedMissing Number5 tests · beginnerNumber of 1 Bits5 tests · beginnerCounting Bits5 tests · intermediateReverse Bits5 tests · intermediatePower of Two4 tests · beginnerPower of Four4 tests · intermediateBitwise AND of Numbers Range5 tests · advancedSum of Two Integers5 tests · advancedDivide Two Integers5 tests · advancedGray Code5 tests · intermediateUTF-8 Validation5 tests · advancedCount Triplets That Can Form Two Arrays of Equal XOR5 tests · advancedMinimum Flips to Make a OR b Equal to c5 tests · intermediateFind XOR Sum of All Pairs Bitwise AND5 tests · advancedDecode XORed Array5 tests · beginnerXOR Queries of a Subarray5 tests · intermediate
←previousIntervals↑ CovernextMatrix problems→