E ExamMaster

Data Structures & Algorithms · Data Structures & Algorithms

B-Trees

Multi-way search trees of order m: node occupancy, search, insert-with-split, delete-with-borrow-or-merge, and why disk indexes use them.

Six concepts on B-trees — order and occupancy, multi-way search, insert-with-split, delete-with-borrow-or-merge, why fan-out cuts I/O, and the B+ fork. Running example: an order-3 tree grown from keys 10, 20, 30, 25, 5, 15.

  • Data Structures & Algorithms
  • Hard level
  • 6 concepts

1Order m: many keys in one node

A B-tree of order m is a multi-way search tree. Every node has at most m children and therefore at most m-1 keys, stored in sorted order. Every node except the root has at least \lceil m/2 \rceil children and \lceil m/2 \rceil - 1 keys. All leaves sit at the same depth, so the tree is perfectly balanced in levels even though a node may hold one key or many.

The root is the occupancy exception: it may have as few as one key and two children (or be a single leaf when the tree is tiny). That is how a B-tree is allowed to start.

This topic uses the children convention for m. Some books call m the maximum number of keys instead — count children before you count keys.

Figure. Order-3 B-tree used for the rest of the topic. Root holds 10 and 20; three leaves hold 5, then 15, then 25 and 30. All leaves at one depth.

What order 3 allows

  1. CapAt most 3 children and 2 keys in any node.
  2. Floor (non-root)At least 2 children and 1 key.
  3. Same depthEvery leaf is at the same level — there is no AVL-style zigzag spine.
Order-3 occupancy
Kind of nodeKeysChildren
Any node, maximum23
Non-root, minimum12
Root, minimum1 (or 0 if the tree is a single empty leaf)2 (or 0)

Keys versus children

A non-root node in an order-3 B-tree holds keys 10 and 20. How many children must it have, and what key ranges do those children cover?

  • keys in the node2 keys \Rightarrow 3 children
  • left childkeys < 10
  • middle childkeys between 10 and 20
  • right childkeys > 20

Pro tip. A node with k keys always has k+1 children if it is internal. Drawing k children is the usual off-by-one.

In a B-tree of order m (max children), a non-root node has at least how many keys?
  1. \lceil m/2 \rceil - 1
  2. m - 1
  3. \lceil m/2 \rceil

Minimum children is \lceil m/2 \rceil, and keys are one fewer than children. m-1 is the maximum, not the minimum. \lceil m/2 \rceil is the child floor, not the key floor.

3Insert splits an overfull node at its median

Insert searches to a leaf and writes the new key in sorted order. If the leaf then has m keys it is overfull. Split: take the median key, push it into the parent, and leave the keys left of the median in the old node and the keys right of the median in a new sibling. The parent gains one key and one child. If the parent overflows, split it the same way.

A split at the root is the only way the tree grows a new level — B-trees grow at the top. Leaves do not sprout downward.

An order-3 B-tree leaf holding 10 and 20. 30 slides in to make an overfull three-key leaf. 20 lifts to become a new root; the leaf splits into 10 on the left and 30 on the right, both still at one depth.
Order 3 allows two keys. Inserting 30 overflows the only node; median 20 rises and the tree grows a new root.

Insert 30 into [10, 20]

  1. Write in the leafThe only node is [10,20]. 30 belongs at the end: [10,20,30].
  2. OverflowOrder 3 allows 2 keys. Three keys is illegal.
  3. Median upMedian 20 becomes a new root. Left leaf [10], right leaf [30].

Split a full child

def split_child(parent, i, full):
    mid = len(full.keys) // 2
    median = full.keys[mid]
    right = Node(keys=full.keys[mid + 1:],
                 children=full.children[mid + 1:])
    full.keys = full.keys[:mid]
    full.children = full.children[: mid + 1]
    parent.keys.insert(i, median)
    parent.children.insert(i + 1, right)

Insert 10, 20, 30 then 25, 5, 15

Order-3 B-tree, start empty. After 10, 20, 30 the root is [20] with leaves [10] and [30]. Insert 25, 5, then 15. What is the tree after 15?

  • 25 into [30]right leaf [25,30]; no split
  • 5 into [10]left leaf [5,10]; no split
  • 15 into [5,10][5,10,15]overflow; median 10 moves up
  • root [20] becomes [10,20]leaves [5], [15], [25,30]

Pro tip. 15 did not create a new level. The median moved into an existing root that still had room. A new level appears only when the root itself splits.

Coding lab. Split the overfull order-3 leaf runs in the app, with checks on your output.

A B-tree grows a new level when
  1. The root splits — a new root is created above it
  2. A leaf splits — leaves sprout a deeper level
  3. Any node reaches \lceil m/2 \rceil keys

A leaf split pushes a median into the parent and stays at the same depth. Only a root split adds a level. \lceil m/2 \rceil keys is the non-root minimum, not a split trigger.

4Delete: borrow through the parent, or merge

Delete starts by locating the key. If it sits in an internal node, replace it by its in-order predecessor (or successor) in a leaf and delete that leaf copy — the same idea as BST delete, but the replacement lives in a leaf. After the leaf loses a key, check occupancy.

If the leaf still has at least \lceil m/2 \rceil - 1 keys, stop. If it underflows, try to borrow: a sibling with a spare key rotates one key through the parent separator. If no sibling can spare a key, merge the underflowing node with a sibling and pull the parent separator down into the merged node. A merge can underflow the parent, so delete walks up just as AVL delete does.

An order-3 B-tree with root 10, 20 and leaves 5, 15 and 25, 30. 15 disappears and the middle leaf is empty. 20 slides down into that leaf and 25 slides up into the root. The close frame is root 10, 25 with leaves 5, 20 and 30.
Delete 15 underflows the middle leaf. The left sibling cannot lend; the right sibling rotates 25 up and 20 down through the parent.

Delete 15 (borrow from the right)

  1. Drop the leaf key[15] becomes empty — below the 1-key floor.
  2. Left cannot lend[5] already has the minimum one key.
  3. Right can lend[25,30] has a spare. Separator 20 moves down; 25 moves up. Leaves become [5], [20], [30]; root becomes [10,25].

Borrow after deleting 15

Order-3 tree: root [10,20], leaves [5], [15], [25,30]. Delete 15. Report the root and the three leaves after the borrow.

  • remove 15middle leaf empty — underflow
  • left sibling [5]at minimum; cannot borrow
  • right sibling [25,30] lends through 2020 down, 25 up
  • finishroot [10,25]; leaves [5], [20], [30]

Pro tip. The borrowed key is not a sibling's edge key sliding sideways. It always passes through the parent, so search order (left < separator < right) stays true.

When a B-tree leaf underflows and a sibling has a spare key, the repair is
  1. Borrow: rotate a key through the parent separator
  2. Move the sibling's nearest key directly into the empty leaf, leaving the parent unchanged
  3. Always merge — borrow is only for internal nodes

Borrow always uses the parent separator as the stepping stone. Skipping the parent breaks the between-keys invariant. Merge is the fallback when no sibling has a spare.

5Fan-out cuts I/O, not just pointer hops

A binary AVL of a million keys has height about 20. A B-tree of order m = 101 (100 keys per node) has height about \log_{101}(10^6) \approx 3. On disk each node is a page, so those numbers are page reads. That is the whole reason B-trees exist: one I/O should move you across many keys.

Pick m from the page size, not from taste. A 4 KiB page holding 8-byte keys and 8-byte child pointers supports on the order of a hundred children. Shrinking m to 3 for a disk index throws the I/O advantage away.

Figure. Nodes visited to search 10^6 keys: about 20 in a binary tree, 3 in an order-101 B-tree. On disk those bars are page reads.

Height for n = 10^6

  1. Binary\lceil \log_2 10^6 \rceil \approx 20 levels.
  2. Order 101\lceil \log_{101} 10^6 \rceil = 3 because 101^2 = 10201 and 101^3 \approx 1.03 \times 10^6.
  3. Read as I/OThree page reads versus twenty, assuming one node per page.

A million keys

Compare worst-case nodes visited to search n = 10^6 keys in a binary tree versus a B-tree of order 101.

  • \log_2 10^6\approx 19.93 → 20 levels
  • 101^210201
  • 101^31030301 > 10^6 → 3 levels
  • I/O if one node = one page20 pages vs 3 pages

Pro tip. The O(m) scan inside a page is CPU. The O(\log_m n) walk is I/O. Disk indexes optimise the second number.

A million keys in a B-tree of order 101 need about how many node visits to search?
  1. 3 — because 101^3 > 10^6
  2. 20 — binary height, reused because each node is binary-searched
  3. 101 — one visit per possible child

101^3 \approx 1.03 \times 10^6, so three levels suffice. Twenty is the binary height. You follow one child per level, not 101 children.

6Filesystems and databases, and the B+ fork

B-trees (and their B+ cousins) sit under filesystems and database indexes because a node is sized to a disk page and a search is a handful of I/Os. That is a different job from an in-memory AVL: same word balanced, different unit of cost.

A classical B-tree may store a record pointer with a key at any level, so a hit can stop early. A B+ tree keeps every record pointer in the leaves, uses internal nodes only as a router, and usually links the leaves so a range scan walks a list instead of re-descending. Production databases almost always pick B+. This topic stays with the classical B-tree algorithms; the B-versus-B+ structural split is the indexing lesson, not a second copy of it.

Figure. Qualitative fit, not a shared unit: AVL for RAM maps, B-tree for disk point lookups, B+ when the workload is range scans on disk.

Which tree for which store

  1. RAM, ordered mapAVL or red-black — pointer hops are cheap.
  2. Disk pagesB-tree or B+ tree — fan-out sized to the page.
  3. Range scans on diskB+ tree, because leaves are linked and hold every key.
B-tree versus B+ (one row each)
KindWhere keys / data liveRange scan
Classical B-treeKeys (and often record pointers) at every levelRe-walk the tree
B+ treeRecord pointers only in leaves; internals routeWalk the leaf linked list
A classical B-tree (not B+) may report a hit
  1. At an internal node, because a key there can carry a record pointer
  2. Only at a leaf — that is the B-tree rule
  3. Only after scanning every leaf, because internals store no keys

Classical B-trees store keys at every level. Restricting data pointers to leaves is the B+ rule. Internals in a B-tree do store keys.

Notes

  • A B-tree of order m is a search tree in which every node has at most m children and every non-root has at least \lceil m/2 \rceil children. All leaves sit at the same depth.
  • A node with k children stores k-1 keys in sorted order. Maximum keys in a node is m-1; a non-root has at least \lceil m/2 \rceil - 1 keys.
  • Search scans the keys in a node and follows one child — O(m) work per node, O(\log_m n) nodes on the path.
  • Insert writes the key in a leaf. An overfull node (m keys) splits: the median key moves to the parent and the remaining keys become two legal nodes.
  • Delete that underflows a node either borrows a key from a sibling through the parent or merges with a sibling and pulls the parent separator down.
  • High fan-out makes height a handful of pages, which is why filesystems and databases use B-trees (or B+ trees) rather than binary AVLs on disk.

Formulas

  • Order m: at most m children, at most m-1 keys per node.
  • Non-root occupancy: at least \lceil m/2 \rceil children and \lceil m/2 \rceil-1 keys.
  • Height in nodes visited: O(\log_m n) = O(\log n / \log m).
  • Split: a node with m keys sends its median to the parent and becomes two nodes of \lfloor (m-1)/2 \rfloor and \lceil (m-1)/2 \rceil keys.
  • I/O cost of a search is the height in pages, not the binary height.

Exam traps & shortcuts

  • Order m here means maximum children, not maximum keys — say which convention you are using before counting.
  • The root is allowed to be poorer than \lceil m/2 \rceil children; every other node is not.
  • Insert splits bottom-up; a split at the root is the only way the tree grows a level, so B-trees grow at the root, not at the leaves.
  • B+ trees keep every record pointer in the leaves and link the leaves; a classical B-tree may store a record pointer with a key at any level. Do not swap those rules.

Reference tables

Children convention — restated from the occupancy concept.

Order-m identities
QuantityForm
Max children / keysm / m-1
Non-root min children / keys\lceil m/2 \rceil / \lceil m/2 \rceil-1
Internal node with k keysk+1 children
Search nodes visitedO(\log_m n)
Split triggera node that has reached m keys
New levelonly when the root splits

The instance the insert and delete ledgers share.

Running order-3 tree
AfterRootLeaves
10, 20[10,20] (single node)
+30 (split)[20][10], [30]
+25, +5, +15[10,20][5], [15], [25,30]
delete 15 (borrow)[10,25][5], [20], [30]

Recap

Night-before B-tree pegs.

Order
m = max children. Non-root has \ge \lceil m/2 \rceil children. All leaves at one depth.
Search
Scan keys, follow one child. O(\log_m n) nodes — that is the I/O count on disk.
Insert
Write in a leaf; split at the median; grow a level only at the root.
Delete
Borrow through the parent separator, or merge and pull the separator down.
Why
10^6 keys: ~20 binary levels vs ~3 at m=101.
B+
Data only in linked leaves. Classical B-tree may hit at an internal node.

Practise B-Trees

Reading is free and needs no account. Practice, mocks and progress live in the app.

  • A 6-question practice set that ends the chapter
  • 6 quick checks with worked explanations
  • Timed mocks scored with the real marking scheme
  • Readiness tracked per topic, kept on your device
Continue with Google — freeNo card, no trial. Works offline once installed.