skip to content

Explain what fanout means for a B+tree index, how it is determined, and why an index over a billion rows is typically only three or four levels deep.

level: middleimportance: must knowfreq 64%

answer

  1. fanout ≈ page bytes / entry bytes
  2. 500^3 = 125M, 500^4 = 62B
  3. height ≈ log_fanout(N)
  4. Wide keys → lower fanout → taller, fatter index
  5. Internal levels ≈ 1/fanout of total size

basics

~20 s

Fanout is how many children one node points to, roughly page size divided by entry size — often several hundred. Height is about log-base-fanout of the row count, so with fanout ~500 three levels index ~125 million keys and four levels index billions.

solid answer

~50 s

**Fanout** is the branching factor: how many child pointers fit in one node. Since a node is one page, fanout ≈ page size ÷ (key size + pointer size + per-entry overhead). With an 8 KB page and roughly 16-byte entries you get on the order of 400-500 children per node. **Height** follows directly: a tree of height h indexes about f^h keys, so h ≈ ceil(log_f N). With f = 500: level 1 root fans to 500 leaves, level 2 to 250,000, level 3 to 125 million, level 4 to 62 billion. That is why "3-4 levels covers everything" is the standard rule of thumb — and why doubling table size almost never adds a level; you need roughly a 500x growth for that. The practical consequence: a point lookup is a small, *constant-ish* number of page accesses regardless of table size. Growth is logarithmic with a very large base, so index lookup cost is effectively flat while a full scan cost grows linearly.

code

text · 8 lines
text
page            = 8192 bytes
entry (key+ptr) = ~16 bytes  -> fanout ~ 500 (less, after headers/fill factor)

levels : addressable keys
  1    : 5e2
  2    : 2.5e5
  3    : 1.25e8
  4    : 6.2e10   <- a billion-row table lands here

go deeper

for a junior

Know that fanout is hundreds of children per node and that this makes trees only a few levels deep, so lookups are cheap.

for a middle

Be able to derive it: page size ÷ entry size = fanout, then powers of fanout versus row count to get height.

for a senior

Use the arithmetic to reason about key width, index footprint, cache residency and how many of the level reads are actually physical.

for a principal

Discuss the tradeoff space — page size, key design, fanout versus contention and read amplification — and when a different structure beats a B+tree for the workload.

## Definition **Fanout** (branching factor) is the number of child pointers a single B+tree node holds. Because a node is exactly one storage page, fanout is a byte-budget question: ``` fanout ≈ usable bytes per page / (key bytes + child pointer bytes + per-entry overhead) ``` With an 8 KB page, an 8-byte integer key, a 6-8 byte page pointer and a few bytes of slot/header overhead, an entry costs on the order of 16-20 bytes, so roughly 400-500 entries fit. Pages are never packed to 100% — engines leave free space for future inserts and pay header and slot-directory overhead — so real fanout is somewhat below the arithmetic maximum. ## Height as a function of fanout A balanced tree of height h with fanout f reaches about `f^(h-1)` leaf pages, and each leaf itself holds many entries. Ignoring that extra leaf capacity for a conservative estimate: ``` height ≈ ceil( log_f(N) ) ``` With f = 500: | levels | keys addressable (approx) | |---|---| | 1 | 500 | | 2 | 250,000 | | 3 | 125,000,000 | | 4 | 62,500,000,000 | So three levels covers a hundred-million-row table and four levels covers tens of billions. This is the arithmetic behind the common statement that production B+trees are almost always 3-4 levels deep. ## Why the base of the logarithm is the whole story Every search structure on disk costs roughly one random page read per level. A binary search tree over one billion keys is ~30 levels — 30 potential random reads. A B+tree with fanout 500 is 4 levels. Same asymptotic class, wildly different constants: `log2(10^9) ≈ 30` versus `log500(10^9) ≈ 3.6`. The engine is trading CPU (binary search *inside* a 8 KB page, which is free relative to I/O) for a 10x reduction in page touches. That is the central design insight of disk-oriented indexing: make each page you pay for eliminate as much of the search space as possible. ## Consequences you can reason with in an interview **Lookup cost is effectively flat.** Because height moves logarithmically with a base in the hundreds, going from 10 million to 100 million rows usually does not add a level. The table has to grow by roughly the fanout factor to push the tree one level deeper. Meanwhile a full table scan's cost grows linearly. This is why index lookups stay cheap as data grows and scans do not. **Key width is a first-class cost.** Fanout is inversely proportional to entry size. Replace an 8-byte integer key with a 40-byte composite or textual key and fanout drops from ~500 to ~150; the tree may gain a level, and — more importantly — the entire index gets several times larger in pages, so much less of it fits in memory. **Page size shifts the tradeoff.** A larger page raises fanout and can shave a level, at the cost of reading and caching more bytes per access and more contention per page. Engines with 16 KB pages naturally reach higher fanout than ones with 4 KB pages. **Only the leaf level is proportional to row count.** The leaf level holds one entry per indexed row; every level above it is smaller by a factor of f. That means the internal levels together are roughly 1/f of the index size — under 1% for typical fanout. So the branch structure is tiny and stays resident in the buffer pool, while the leaf level is the part that may not fit in memory. ## Estimating on a whiteboard A clean way to answer aloud: "A page is 8 KB. My key plus pointer is about 16 bytes, so a node fans out to roughly 500 children. 500^3 is 125 million and 500^4 is 62 billion, so a billion-row table is a 4-level tree. A point lookup is 4 page accesses, of which the top 2-3 are cached, so realistically about one physical read plus the row fetch." That chain of reasoning — page size → entry size → fanout → powers → height → I/O — is exactly what the question is testing. ## Common trap Candidates sometimes say height is `log2(N)` because they are reasoning from binary trees, or claim height grows meaningfully as the table grows. Both miss the point: the base of the logarithm is in the hundreds, which is why B+tree depth is nearly a constant across every real-world table size.

  • If the table doubles in size, what happens to index lookup cost?
    Essentially nothing. Height only increases when the row count grows by roughly a factor of the fanout — hundreds of times, not two. The leaf level doubles in size, so more of it may spill out of cache, but the number of levels traversed stays the same. Cost is logarithmic with a base in the hundreds, which behaves like a constant across realistic growth.
  • How does replacing an 8-byte integer key with a 36-character UUID string affect the tree?
    Entry size grows several fold, so fanout falls proportionally — perhaps from ~500 to ~150. That can add a level and, more significantly, multiplies the index's page count, so a much smaller fraction of it stays cached. Both effects raise the number of physical reads per lookup, and every secondary index that references the key inherits the cost.
  • Which level of the tree consumes most of the index's storage, and why does that matter?
    The leaf level, which holds one entry per indexed row; each level above is smaller by a factor of the fanout, so all internal levels together are under about one percent of the index. This means the routing structure is cheap to keep in memory permanently, and cache pressure is really about how much of the leaf level fits. Sizing discussions should focus on leaf-level bytes.

A tournament bracket where each round eliminates 499 of 500 contenders instead of half — you reach a single winner in four rounds instead of thirty.

saying these in an interview costs you the question

  • Saying index height is log2(N) — that is binary-tree reasoning, not B+tree
  • Claiming the tree gets meaningfully deeper as the table grows day to day
  • Ignoring key width when estimating fanout
  • Assuming every level costs a physical disk read, when upper levels are effectively always cached
  • Treating fanout as a fixed constant of the engine rather than page size divided by entry size

context