CMD Guide
HomeDatabasesIndexing & Storage

How Indexes Work — B+tree Internals

Why a lookup needs an index at all

WHERE id = 42 on an unindexed table is a full table scan — read every row, O(n). An index is a separate sorted structure that turns that into O(log n). Almost every relational database uses a B+tree for it — here's why and how.

Why a B+tree, not a binary tree or a hash

Disks (and SSDs) read in pages (~8 KB), and the cost is the number of page reads. A binary tree wastes that — one key per node, ~30 levels for a billion rows = ~30 reads. A B+tree packs hundreds of keys into one page (high fanout), so the tree is only ~3–4 levels deep for a billion rows → ~3–4 page reads per lookup. A hash index does equality in O(1) but can't do ranges or ORDER BY — which is why B+tree is the default.

A B+tree: root routing node points to sorted leaf nodes linked left-to-right; a lookup of 60 descends root to the middle leaf, range scans follow the leaf links
A B+tree: root routing node points to sorted leaf nodes linked left-to-right; a lookup of 60 descends root to the middle leaf, range scans follow the leaf links

Structure & the two operations

Point lookup (=): start at the root, follow the pointer for your key's range down to the leaf — ~3–4 reads. Range scan / ORDER BY / BETWEEN: descend to the start leaf, then walk the leaf links sideways — this sideways chain is exactly why B+tree beats hash for ranges and sorted output.

Trace: looking up key 60

  1. Root. Routing keys are [40, 80]. 60 falls in the 40 ≤ key < 80 range, so follow the middle child pointer.
  2. Internal/leaf. The child node contains [40, 50, 60, 70]. 60 is present, so the leaf entry gives the row pointer (or the row itself if the index is clustered).
  3. Range continuation. A query for WHERE key BETWEEN 60 AND 100 would stop at the 60 entry, then use the leaf's right-link to scan 70, 80, 90, 100 sequentially without climbing back to the root.

Inserts & Deletes: The Mechanics of Splits and Merges

B+trees remain perfectly balanced because they grow from the bottom up during insertions and shrink during deletions. When a node exceeds its maximum capacity, it splits; when a node falls below minimum capacity, it merges or redistributes keys.

🌳 Step-by-Step B+tree Structural Traces (Order $M=4$)
(A node can hold a maximum of $M-1 = 3$ keys and $M = 4$ pointers. Minimum capacity for a node is $\lfloor M/2 \rfloor = 2$ pointers.)

1. Leaf Node Split Trace

Suppose we have a leaf node A currently at max capacity holding keys: [10, 20, 30]. We insert key 25:
  1. Overflow State: The keys temporarily become [10, 20, 25, 30] (exceeding 3 keys).
  2. Allocate Node: A new leaf node A2 is allocated.
  3. Split Keys: The first $\lceil (M-1)/2 \rceil = 2$ keys stay in A: [10, 20]. The remaining keys go to A2: [25, 30].
  4. Adjust Leaf Links: A's next pointer is set to point to A2, and A2's next pointer points to the original successor of A.
  5. Promote Key: The smallest key in the new leaf (25) is copied up into the parent node to act as a routing separator. (Notice that 25 remains in the leaf A2 because leaves must contain all data).

2. Internal Node Split Trace

Suppose the parent internal node was already full holding routing keys [15, 40, 70] with four child pointers: P0, P1, P2, P3. We attempt to push key 25 up from the leaf split:
  1. Overflow State: The internal node keys temporarily become [15, 25, 40, 70] (exceeding 3 keys).
  2. Allocate Node: A new internal node Parent2 is allocated.
  3. Split and Move Key: The first key [15] remains in Parent. The middle key 25 is moved (pushed) up to the grandparent node. unlike leaf splits, 25 is removed from this level. The remaining keys [40, 70] move to Parent2.
  4. Reassign Pointers:
    • Parent keeps pointers: P0 (for keys $< 15$) and P1 (for $15 \le \text{key} < 25$).
    • Parent2 gets pointers: P2 (for $25 \le \text{key} < 40$), P3 (for $40 \le \text{key} < 70$), and the new child pointer P4 (for $\text{key} \ge 70$).

3. Page Merge (Coalescence) Trace

When a key is deleted, a node might fall below minimum capacity. Suppose we delete 10 from leaf [10, 20], leaving it with only [20] (underflow):
  1. Check Siblings: The underflow node looks at its right sibling B.
  2. Redistribution (Borrowing): If B has keys [30, 40], it has surplus. The underflow node borrows 30. The parent separator key is updated from 30 to 40.
  3. Merging: If B has only [30] (no surplus), the nodes must merge. We combine them into a single leaf: [20, 30]. The separator key in the parent is deleted. If this causes the parent to underflow, the merge propagates up the tree.

Clustered vs secondary (the hop that surprises people)

When-NOT to reach for a B+tree (and when an extra index hurts)

B+tree is the default for a reason, but it is not free and not always the right structure:

Interview drills

  1. L1: Why ~3–4 disk reads for a billion-row B+tree lookup?
    Answer: High fanout (hundreds of keys per ~8KB page) ⇒ tree height logfanout(n) ≈ 3–4.
  2. L2: Why can B+tree do BETWEEN efficiently and a hash index cannot?
    Answer: Leaves are a sorted linked list; descend to start key then walk sideways. Hash scatters equal ranges across buckets.
  3. L3: You added five secondary indexes "for performance" and writes halved. What happened?
    Answer: Each write updates every secondary leaf (and may split). Write amplification; drop unused indexes or use partial/covering carefully.
  4. L4: Clustered PK is UUIDv4; insert p99 latency climbs with table size. Why, and what would you change?
    Answer: Random inserts scatter across leaves → splits + cache misses. Switch to sequential/time-ordered clustering key; secondary unique on UUID if needed.
  5. L5: When would you argue for an LSM store over B+tree for the primary access path?
    Answer: Sustained high ingest, range scans over time-ordered keys acceptable via SSTables, point reads secondary; accept compaction cost and read amplification trade-off.

Takeaways


Re-authored for this guide; B+tree diagram hand-authored as SVG. Follows CMU 15-445 (Andy Pavlo) and DDIA ch. 3. See also: Indexes in Practice, How a Query Executes, Storage Engines — B-tree vs LSM-tree.

🤖 Don't fully get this? Learn it with Claude

Stuck on How Indexes Work — B+tree Internals? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **How Indexes Work — B+tree Internals** (Databases) and want to truly understand it. Explain How Indexes Work — B+tree Internals from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **How Indexes Work — B+tree Internals** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **How Indexes Work — B+tree Internals** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **How Indexes Work — B+tree Internals** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes