CSE3144 · Advanced Data Structures · Jul–Nov Semester 2026 · Lecture 11 of 36 · CO CSE3144.2

B-Trees and Their Variants

One family of trees, built around a single physical fact: touching a disk is a hundred-thousand times slower than touching RAM. B-trees, B+ trees, and B* trees are three answers to the same question — how do you stay balanced while paying for that as rarely as possible?

Dr. Manu ShrivastavaCourse Instructor · Consultation Fri 2–5 PM, LHC 308F
40 minutesSession outcome: explain B-Trees, B+ Trees, and B* Trees
L11 · 00 — Agenda 47 min total

Today, minute by minute

Refresher: why disk breaks the O(log₂n) promise

00–04

AVL/RB height is fine in RAM; on disk, every node is a seek. The printed-dictionary analogy.

B-tree definition: order, occupancy, uniform depth

04–07

The rules, stated once, that every animation below just applies.

Animation: building an order-3 B-tree, keys 1–10

07–14

Every insertion, every split, the cascading double-split at key 7.

Animation: deleting from that same tree

14–21

Simple delete, merge, borrow-through-parent, and the tree shrinking a level.

Why B+ trees? The range-query weakness

21–23

Data scattered across a B-tree makes "give me everything from 20 to 80" expensive.

B+ tree structure + animation: same 1–7, rebuilt

23–29

Index copies vs real data; copy-up vs move-up; the linked leaf chain.

Animation: B+ deletion and the index-update rule

29–32

Borrowing across leaves means fixing the signpost above them too.

B-tree vs B+ tree, side by side

32–34

Where data lives, and why it decides everything else.

Why B* trees? The wasted-space problem

34–36

Half-full nodes are the norm, not the exception, after enough deletes.

Animation: B* insertion — redistribute first, split only if forced

36–40

The easy case (sibling has room) and the hard case (two-to-three split), both on one tree.

Animation: B* deletion — borrow, then the three-into-two collapse

40–45

Why two min-occupied nodes can't just merge into one — and what happens instead.

Recap & what's next

45–47

One idea worn three ways. Lecture 12: Segment and Interval Trees.

CSE3144 — Lecture 11
L11 · 01 — Refresher & the need ~4 min

Why balanced isn't enough once data lives on disk

Refresher — what O(log n) actually cost you

Lectures 7–9: AVL and Red-Black trees hold height to O(log₂n) by capping every node at 2 children. For n = 1,000,000 records that's a height of about 20 — brilliant, as long as the whole tree sits in RAM.

But databases, filesystems, and search indexes don't fit in RAM. They live on disk. And "one node visited" on a root-to-leaf path now means "one possible trip to the disk" — which changes the arithmetic completely.

The disk fact that changes everything

A RAM access takes on the order of 100 nanoseconds. A disk seek takes 5–10 milliseconds — roughly 100,000× slower. A height-20 binary tree can mean 20 seeks per search: at 8ms each, that's 160ms for one lookup — brutal for a system answering thousands of queries a second.

And disks don't read "one key" at a time — they read in fixed-size blocks (typically 4–8 KB) no matter how little you asked for. So the right size for "one tree node" isn't one key; it's as many keys as fit in one block.

Analogy — looking up a word in a printed dictionary: imagine an absurd dictionary printed with one word per page. Finding "MERGE" means flipping to a page, reading its single word, deciding "earlier or later?", and flipping again — around 17 flips for 100,000 words, and every flip is physical work. That is a binary tree on disk: one key per node, one seek per node. A real dictionary instead packs hundreds of sorted words on each page, with guide words at the top corner. You flip to a page (that's the trip — the expensive part), then scan dozens of entries on the page for free — your eyes are already there — and the guide words tell you exactly which page to flip to next. Three or four flips finds any word among 100,000. That wide, shallow, many-keys-per-page shape is exactly a B-tree: page = disk block, page-flip = disk seek, within-page scanning = free RAM comparisons. Trade tree height for node width, because on disk it's the number of trips that costs you, not the number of comparisons inside a trip.

CSE3144 — Lecture 11
L11 · 02 — B-tree: the definition ~4 min

A search tree where every node is a full disk block

Properties of an order-m B-tree
  • Every node holds up to m − 1 sorted keys and up to m children.
  • A node with k keys has exactly k + 1 children; child i holds every key strictly between key i−1 and key i — the same "between the keys" rule from ordinary BSTs, just generalized past 2 children.
  • Every node except the root must have at least ⌈m/2⌉ children (at least ⌈m/2⌉ − 1 keys) — no node may be less than half full.
  • Every leaf sits at exactly the same depth — the tree never gets lopsided.
Every property, visible on one legal order-5 tree
  • Order 5 ⟹ max 4 keys per node — the middle leaf [50 60 70 75] is exactly full.
  • Min ⌈5/2⌉ = 3 children ⟹ min 2 keys — the right leaf [90 95] sits at the legal minimum; one key fewer and it would violate the half-full rule.
  • Root [40 80] has 2 keys ⟹ exactly 3 children, and each child's keys fall strictly in its gap: <40, between 40 and 80, >80.
  • All three leaves at the same depth — count it.
CSE3144 — Lecture 11 · B-tree definition
L11 · 02a — The working order, and the search operation ~4 min

Order 3 as the working example — and search, step by step

The order used in this lecture's examples

All worked examples in this lecture use m = 3, the smallest non-trivial order: max 2 keys / 3 children per node, min 1 key / 2 children (except the root). A small order keeps every split and merge fully visible within a few steps. Production database and filesystem B-trees use m in the hundreds or thousands, sized so one node = exactly one disk block.

The insertion and deletion rules are identical for every order. Master the order-3 traces that follow, and the same procedure applies unchanged at order 500 in production — only the arithmetic scales, not the idea.

Search — the one operation that needs no new machinery

At each node, scan its sorted keys to find which of the k + 1 gaps your target falls into, then descend into that child. Repeat until found — or until you're standing in a leaf with nowhere left to descend (a definitive "not present"). The animation traces both outcomes on an order-3 tree. The only genuinely new machinery in this lecture is keeping every node between half-full and full as you insert and delete — that's what the next two animations handle.

Interactive — search 50 (a hit), then search 85 (a miss)
CSE3144 — Lecture 11 · Search
L11 · 03 — Animation: building a B-tree ~7 min

Insert 1 through 10 into an order-3 B-tree — watch every split

The only rule you need
insert k into its correct leaf (descend by
    the SEARCH of L11·02a: at each node, scan
    the sorted keys, take the gap k falls in)

if that leaf now holds m keys (one too many):
    split it at the median:
        left half keeps the smaller keys
        right half keeps the larger keys
        median key moves UP into the parent
    // if the parent now overflows too, split IT
    // the same way — this can cascade to the root
    // if the root overflows, a NEW root is created
    // and the tree grows one level taller — upward
How to read the animation

Order 3 means max 2 keys per node. A node briefly holding 3 keys is shown with a dashed red border — overflow, must split now. Amber boxes are leaves; teal boxes are internal (routing) nodes. Watch the median: it always escapes upward, never sideways.

Interactive — insert 1, 2, 3, … , 10 one key at a time
CSE3144 — Lecture 11 · B-tree insertion
L11 · 04 — Animation: deleting from a B-tree ~7 min

Delete 10, 9, 5, 4 — every deletion case, one tree

The four cases, in the order you'll meet them
  • No underflow: the leaf still has ≥ 1 key after removal — done.
  • Merge: leaf empties, sibling is also at the minimum — merge them, pulling the parent's separator key down between them.
  • Borrow through the parent: leaf empties, but a sibling has a spare key — rotate one key down from the parent and one up from the sibling.
  • Delete an internal key → successor swap, then repair: a key can only be physically removed at a leaf, so replace it with its inorder successor (always in a leaf), then fix that leaf's underflow — the repair can cascade upward and shrink the tree by a whole level.
Why this order was chosen — and how to read the animation

This is the exact same tree you just built. Deleting 10 → 9 → 5 → 4 in sequence walks you through all four cases without ever introducing a new example — by the last step you'll have personally traced why deleting one internal key can ripple all the way to the root. Every removal is shown as its own step before the repair: a dashed red node marks underflow — a node below its key minimum (· = empty) that the next step must fix.

Interactive — delete 10, then 9, then 5, then 4, then 7, then 3
CSE3144 — Lecture 11 · B-tree deletion
L11 · 05 — Why B+ trees? ~2 min

One weakness of a plain B-tree: range queries

The problem

In a B-tree, real data can sit at any node — root, internal, or leaf. So answering "give me every record between 20 and 80" means jumping in and out of internal nodes with no shortcut between neighboring leaves. Yet this exact kind of query is everywhere: SELECT … WHERE age BETWEEN 20 AND 80, listing a directory alphabetically, streaming a large file's blocks in order.

The B+ tree fix

Force all real data into the leaves only. Internal nodes hold nothing but routing copies of keys — pure signposts, never touched by a range scan. Then thread every leaf to its right neighbor with a pointer. Once you've found where a range starts, you just walk the linked leaves left to right. This is exactly how MySQL InnoDB, PostgreSQL, and the NTFS/ext4 directory indexes work — B+ trees, not plain B-trees.

See it, not just read it: run the identical range query — every key from 2 to 7 — on two order-3 trees holding the same six keys (1, 2, 3, 6, 7, 8). The left tree is exactly where Section 04's deletion animation left off; the right tree is a preview of the B+ shape Section 07 builds properly from scratch. Step through both and watch which one ever has to climb back up.

B-tree — range query [2,7]
B+ tree — the same query [2,7]

Both queries return the identical set — {2, 3, 6, 7} — and cost about the same number of disk trips at this tiny scale. What differs is the shape of the trip: the B-tree's path is root → leaf → back to root → leaf, revisiting the top of the tree mid-query. The B+ tree's path is root → leaf → leaf → leaf — one descent, then a straight forward walk that never looks back up. At real scale (millions of keys, ranges spanning thousands of them) that difference is the entire reason range-heavy systems choose B+ trees.

CSE3144 — Lecture 11
L11 · 06 — B+ tree structure ~2 min

Two kinds of node now, not one

Same slot-and-pointer layout, one crucial difference: where does DATA sit?
P₀
K₁data
P₁
K₂data
P₂
K₃data
P₃
a B-tree node — every key carries its own data, whether the node is the root, internal, or a leaf
P₀
K₁no data
P₁
K₂no data
P₂
K₃no data
P₃
a B+ tree INTERNAL node — same P-K-P-K-P slot pattern, but every key is a bare signpost copy
K₁data
K₂data
K₃data
a B+ tree LEAF node — no child pointers at all, just real data and one pointer to the next leaf
Internal (index) nodes

Store only copies of keys, purely to steer a search left or right. Hold no data of their own. Obey the same max-m-children rule as a B-tree.

Leaf nodes

Store the real keys (and, in a database, the record or a pointer to it) — plus one extra pointer to the next leaf, threading every leaf into one sorted linked list.

This double bookkeeping is the trade B+ trees make: a key can exist twice — once as real data in a leaf, once as a signpost copy in an internal node — and if the data ever moves to a different leaf, its signpost copy must be updated to match. You'll watch that exact update happen in the deletion animation below.

CSE3144 — Lecture 11 · B+ tree structure
L11 · 07 — Animation: building a B+ tree ~6 min

Same keys, 1 through 7 — a different shape

What's different from the B-tree build

A leaf split never removes a key — it copies the smallest key of the new right leaf up as the separator (the data must stay in a leaf). How many keys land on each side follows one rule at every order: when a leaf overflows to m keys, the left leaf keeps ⌈m/2⌉ of them and the right leaf keeps ⌊m/2⌋. Order-3's overflow of 3 keys splits 2-left/1-right, so the copied-up key — the smallest of the right leaf — happens to also be the largest of the three, as the animation below shows. That's a coincidence of the 1-key right half, not the rule: at order 4 (4 keys → 2-and-2) or order 5 (5 keys → 3-and-2), the copied-up key is just the first key of whichever keys landed in the right half, no longer the largest overall. An internal split moves its true median up, exactly like a B-tree (there's no data attached to a signpost, so nothing needs to stay behind).

Watch the leaf chain

Below every tree snapshot you'll see the leaf-to-leaf linked list spelled out. However tangled the tree above gets, that chain always stays a single, unbroken, sorted sequence — the whole point of a B+ tree.

Interactive — insert 1, 2, 3, … , 7 into an order-3 B+ tree
CSE3144 — Lecture 11 · B+ tree insertion
L11 · 08 — Animation: B+ tree deletion ~3 min

Delete 4, then 7, then 6 — signpost repair, then a merge

The B+-only rule to watch for

Deleting a key that was never copied into an internal node is exactly as simple as a leaf-only delete gets. Deleting one that borrows across a boundary is different: after the borrow, the internal signpost above it is now stale and must be rewritten to the new smallest key of the leaf it points to. When neither sibling has a key to spare, borrowing isn't an option — the leaves merge instead, and the parent's separator is simply discarded (it was only ever a copy, so nothing needs to move down). If that empties the parent's own key count, the underflow cascades one level up — and there the separator does get pulled down, exactly like a plain B-tree merge, because an internal node carries no data of its own to protect.

Why this matters in practice

Every insert or delete near a leaf boundary in a real database index pays this small bookkeeping cost. It's the price of keeping the leaf chain always walkable in order — cheap per operation, and the reason range queries stay fast forever.

Interactive — delete 4, then 7, then 6
CSE3144 — Lecture 11 · B+ tree deletion
L11 · 09 — B-tree vs B+ tree ~2 min

Where data lives decides everything else

AspectB-treeB+ tree
Where real data livesAny node — root, internal, or leafLeaves only
Internal node contentReal keys, with data attachedRouting copies only, no data
Range query / sequential scanMust revisit internal nodes repeatedlyOne hop to the next leaf via the linked list
Search cost (same order m)O(logm n)O(logm n), then a walk of the leaf level
Space overheadNo key duplicationSome duplication — routing copies
Typical real-world useSome in-memory / general-purpose indexesAlmost every production DB index (MySQL InnoDB, PostgreSQL) and filesystem directory index (NTFS, ext4)
CSE3144 — Lecture 11 · Comparison
L11 · 10 — Why B* trees? ~2 min

The wasted-space problem

What repeated inserts and deletes leave behind

A split only guarantees a node stays at least half full (⌈m/2⌉ − 1 keys minimum). After years of real-world churn, most nodes in a live B-tree or B+ tree settle near that 50% floor — meaning roughly half of every disk block read is empty space. At the scale of a production index, that's an enormous amount of wasted I/O and cache.

The B* idea

Delay splitting for as long as possible. Before splitting a full node, first check whether a sibling has spare room and shift keys sideways through the parent — an insertion-time version of the borrow-from-sibling trick you already used in deletion. Only split when both a node and its sibling are completely full — and even then, split two full nodes into three roughly-two-thirds-full ones, instead of one full node into two half-full ones.

CSE3144 — Lecture 11
L11 · 11 — B* trees: redistribute first, split only if forced ~4 min

Same skeleton, a stricter occupancy floor

B*-tree of order m — three rules
  • The root has between 2 and 2⌊(2m−2)/3⌋+1 children.
  • Every other internal node has between ⌈(2m−1)/3⌉ and m children — roughly two-thirds full to completely full, not one-half to full.
  • Every leaf sits at the same depth — identical to a B-tree.
The cost of the better packing

Insertion and — especially — deletion logic must now respect a two-thirds floor instead of a one-half floor, which makes both noticeably more intricate to implement correctly. That extra complexity is exactly why B* trees, despite better space usage, are far rarer in production than B and B+ trees. The most famous real user: the classic Apple HFS filesystem's catalog file.

Interactive — redistribute (no split), then the two-to-three split, order m = 4
CSE3144 — Lecture 11 · B* trees
L11 · 11a — Animation: B* tree deletion ~5 min

Why two minimum-occupied nodes can't just merge into one

The mirror of insertion — with one extra wrinkle
  • Underflow, sibling has spare keys: borrow through the parent, exactly like a B-tree — no node count change.
  • Underflow, sibling is also at the minimum: a plain B-tree would just merge the two into one. A B* tree can't — two-thirds-full plus two-thirds-full is more than one node's capacity (for order 4: 2+2 keys don't fit in a max-3 node).
  • So B* deletion pulls in a second sibling too — three nodes at/near the minimum — and redistributes all of them across only two nodes: the exact mirror of the two-to-three split, run backwards.
How to read the animation

Same order-4 tree the insertion animation just built, continued: [1,2] · 3 · [4,5] · 7 · [8,9]. First a quick insert to set up an honest "sibling has spare keys" case, then two deletions — one that borrows cleanly, and one that forces the three-into-two collapse.

Interactive — insert 6, delete 9 (borrows), delete 4 (three-into-two)
CSE3144 — Lecture 11 · B* tree deletion
L11 · 12 — Recap & what's next ~2 min

One idea, worn three ways

AspectB-treeB+ treeB* tree
Minimum occupancy~50%~50% (both levels)~67%
Where data livesAny nodeLeaves onlyAny node
Checks sibling before splittingNoNoYes — the two-to-three split
Real-world exampleGeneral-purpose on-disk indexesMySQL InnoDB, PostgreSQL, NTFS, ext4Classic Mac OS HFS catalog file

Every idea in this lecture is really one idea worn three ways: keep the tree short (wide branching, not two children) and keep every node densely packed, because on disk the number of nodes touched matters far more than the number of comparisons inside each one. B+ trees additionally chain their leaves so a whole range of answers costs one linked-list walk. B* trees push occupancy further still, at the price of trickier bookkeeping.

Lecture 12 — next

Segment Trees and Interval Trees

From "find one key" to "answer a query about a whole range" — a different kind of tree, still built to answer fast.

Homework — bring to Lecture 12
  • Build an order-3 B-tree by inserting 20, 40, 10, 30, 50, 5, 60. Verify every leaf ends at the same depth.
  • Starting from this lecture's final B-tree (root [2,6], leaves [1], [3], [7,8]) delete 8. Which of the four deletion cases applies?
  • Build an order-3 B+ tree for the same keys as Q1, and write out the leaf chain.
  • In two sentences: why does a B+ tree answer a range query faster than a B-tree of the same order, even though both have the same search height?
  • Reading: CLRS §18 (B-Trees); Weiss §4.7.
CSE3144 — Lecture 11

Questions?

Dr. Manu Shrivastava — LHC 308F — Friday 2:00–5:00 PM

Next: Lecture 12 — Segment Trees and Interval Trees.