Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 9 – Trees and binary search trees

Red-black trees and B-trees (concepts)

The red-black rules that cap a tree at 2 log₂(n + 1) levels, their link to 2-3-4 trees, and why databases store indexes as B-trees of pages.

  • Advanced
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 12 of 35 minutes free
Free: The five red-black rules · Red-black trees are 2-3-4 trees in disguise · Inserting ten keys, traced · 4 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 23 minutes.

What you will learn

  • State the red-black rules and the height bound they guarantee
  • Map a red-black tree to the 2-3-4 tree it encodes
  • Explain why databases store their indexes in B-trees and B+ trees
  • Validate a coloured binary tree against every red-black rule

Before you start

Red-black trees and B-trees are the balanced search trees that most real software runs on. A red-black tree is a binary search tree whose nodes carry one extra bit, a colour, and whose colours obey rules that keep every path from the root within a factor of two of every other: its height is at most 2 log₂(n + 1). It accepts a less even shape than an AVL tree in exchange for fewer rotations, at most two per insertion and three per deletion, and it sits inside Java’s TreeMap and most C++ std::map implementations. B-trees keep hundreds of keys in each node, sized to a disk or memory page, so a billion keys are only four or five levels deep; databases and file systems keep their indexes in them.

The five red-black rules

  1. Every node is red or black.
  2. The root is black.
  3. Every empty subtree counts as a black leaf.
  4. A red node has no red child.
  5. Every path from a node down to an empty subtree passes the same number of black nodes.

Rule 5 makes the black nodes alone a perfectly balanced skeleton, and rule 4 lets at most one red node sit between two black ones, so no path from the root can be more than twice as long as another. That is where the bound 2 log₂(n + 1) comes from. Leo Guibas and Robert Sedgewick described the colouring in 1978.

Red-black trees are 2-3-4 trees in disguise

Merge every red node into its black parent and each group becomes one node of a 2-3-4 tree: a black node with no red child holds one key, with one red child two keys, with two red children three keys. Every path passes the same number of black nodes, so every leaf of that 2-3-4 tree sits at the same depth.

A 10-key red-black tree with black root 14, and the 2-3-4 tree it encodes: root 10, 14, 20 over four leaves.Red-black tree: R = red (circles), B = black (squares)The same keys as a 2-3-4 tree: every leaf at the same depth14 B10 R20 R5 B12 B15 B30 B1 R13 R25 R10 | 14 | 201 | 512 | 131525 | 30merge every red node into its black parent

Each black node and its red children form one node of a 2-3-4 tree

Text description of the diagram

The diagram shows the red-black tree built in the lesson's run and the 2-3-4 tree it stands for, from top to bottom. Red nodes are drawn as circles marked R, black nodes as squares marked B.

  1. The red-black tree has the black root 14. Its children are 10 and 20, both red. Below 10 are the black nodes 5 and 12; 5 has the red child 1 and 12 has the red child 13. Below 20 are the black nodes 15 and 30; 30 has the red child 25.
  2. Every path from the root down to an empty subtree passes exactly two black nodes, and no red node has a red child.
  3. Merging each red node into its black parent gives the 2-3-4 tree: the root holds 10, 14 and 20 and has four children, the nodes 1 and 5, 12 and 13, 15 alone, and 25 and 30. Every leaf of the 2-3-4 tree is at the same depth, which is why the red-black tree stays balanced.

Insertion follows the same picture. A new key arrives red, which leaves every black count unchanged, so the only rule it can break is rule 4. If the red parent’s sibling, the uncle, is red too, the 2-3-4 node is full: recolouring splits it and moves the problem two levels up. If the uncle is black, one or two rotations rearrange the keys of that node, and the repair is over.

Inserting ten keys, traced

Red-black insertion with every repair printed Python · rb/rb.py
"""A red-black tree: insertion with each repair printed, a check of the rules, and the 2-3-4 tree it stands for."""

RED, BLACK = "red", "black"


class Node:
    __slots__ = ("key", "color", "left", "right", "parent")

    def __init__(self, key, color, nil):
        self.key, self.color = key, color
        self.left = self.right = self.parent = nil


class RedBlackTree:
    def __init__(self):
        self.nil = Node(None, BLACK, None)  # one shared black leaf stands for every empty subtree
        self.nil.left = self.nil.right = self.nil.parent = self.nil
        self.root = self.nil
        self.rotations = 0
        self.log = []

    def rotate_left(self, x):
        y = x.right
        x.right = y.left
        if y.left is not self.nil:
            y.left.parent = x
        self._replace(x, y)
        y.left, x.parent = x, y
        self.rotations += 1

    def rotate_right(self, y):
        x = y.left
        y.left = x.right
        if x.right is not self.nil:
            x.right.parent = y
        self._replace(y, x)
        x.right, y.parent = y, x
        self.rotations += 1

    def _replace(self, old, new):
        """Puts new where old hangs from its parent."""
        new.parent = old.parent
        if old.parent is self.nil:
            self.root = new
        elif old is old.parent.left:
            old.parent.left = new
        else:
            old.parent.right = new

    def insert(self, key):
        parent, node = self.nil, self.root
        while node is not self.nil:
            if key == node.key:
                return  # keys are unique
            parent, node = node, (node.left if key < node.key else node.right)
        z = Node(key, RED, self.nil)  # a new node is red, so no path gains a black node
        z.parent = parent
        if parent is self.nil:
            self.root = z
        elif key < parent.key:
            parent.left = z
        else:
            parent.right = z
        self._repair(z)

    def _repair(self, z):
        while z.parent.color == RED:  # two reds in a row: the only rule an insertion can break
            p, g = z.parent, z.parent.parent
            uncle = g.right if p is g.left else g.left
            if uncle.color == RED:  # recolour and move the problem two levels up
                p.color = uncle.color = BLACK
                g.color = RED
                self.log.append(f"red uncle at {g.key}: recolour")
                z = g
                continue
            if p is g.left:
                if z is p.right:  # zig-zag: rotate it into a straight line first
                    self.rotate_left(p)
                    z, p = p, z
                    self.log.append(f"black uncle, zig-zag at {g.key}: two rotations")
                else:
                    self.log.append(f"black uncle, line at {g.key}: one rotation")
                self.rotate_right(g)
            else:
                if z is p.left:
                    self.rotate_right(p)
                    z, p = p, z
                    self.log.append(f"black uncle, zig-zag at {g.key}: two rotations")
                else:
                    self.log.append(f"black uncle, line at {g.key}: one rotation")
                self.rotate_left(g)
            p.color, g.color = BLACK, RED  # the middle key goes up black, the old grandparent becomes red
        if self.root.color == RED:
            self.root.color = BLACK
            self.log.append("root painted black")

    def check(self):
        """Black nodes on every path from the root to an empty leaf; fails on the first broken rule."""
        assert self.root.color == BLACK, "the root is red"

        def walk(n, low, high):
            if n is self.nil:
                return 0
            assert low < n.key < high, f"{n.key} is out of order"
            if n.color == RED:
                assert n.left.color == BLACK and n.right.color == BLACK, f"red {n.key} has a red child"
            left, right = walk(n.left, low, n.key), walk(n.right, n.key, high)
            assert left == right, f"paths below {n.key} hold {left} and {right} black nodes"
            return left + (n.color == BLACK)

        return walk(self.root, float("-inf"), float("inf"))

    def height(self, n=None):
        n = self.root if n is None else n
        return -1 if n is self.nil else 1 + max(self.height(n.left), self.height(n.right))

    def sideways(self, n=None, depth=0, out=None):
        n, out = (self.root if n is None else n), ([] if out is None else out)
        if n is not self.nil:
            self.sideways(n.right, depth + 1, out)
            out.append("    " * depth + f"{n.key} {n.color}")
            self.sideways(n.left, depth + 1, out)
        return out

    def as_234(self):
        """The 2-3-4 tree level by level: each black node merged with its red children into one node."""
        levels, level = [], [self.root] if self.root is not self.nil else []
        while level:
            row, below = [], []
            for b in level:
                keys = []
                for c in (b.left, b, b.right):
                    if c is b:
                        keys.append(b.key)
                    elif c.color == RED:
                        keys.append(c.key)
                        below += [c.left, c.right]
                    else:
                        below.append(c)
                row.append("[" + " ".join(map(str, keys)) + "]")
            levels.append(" ".join(row))
            level = [x for x in below if x is not self.nil]
        return levels


if __name__ == "__main__":
    t = RedBlackTree()
    for key in (10, 20, 30, 15, 25, 5, 1, 12, 14, 13):
        t.log.clear()
        t.insert(key)
        t.check()  # every rule holds after every insertion
        print(f"insert {key:2}: {'; '.join(t.log) or 'parent is black: nothing to do'}")
    print(f"10 keys: height {t.height()}, {t.check()} black nodes on every root-to-leaf path, {t.rotations} rotations")
    print("\n".join(t.sideways()))
    print("the same keys as a 2-3-4 tree, level by level:")
    for row in t.as_234():
        print("   ", row)

Output

insert 10: root painted black
insert 20: parent is black: nothing to do
insert 30: black uncle, line at 10: one rotation
insert 15: red uncle at 20: recolour; root painted black
insert 25: parent is black: nothing to do
insert  5: parent is black: nothing to do
insert  1: red uncle at 10: recolour
insert 12: parent is black: nothing to do
insert 14: black uncle, zig-zag at 15: two rotations
insert 13: red uncle at 14: recolour; black uncle, zig-zag at 20: two rotations
10 keys: height 3, 2 black nodes on every root-to-leaf path, 5 rotations
        30 black
            25 red
    20 red
        15 black
14 black
            13 red
        12 black
    10 red
        5 black
            1 red
the same keys as a 2-3-4 tree, level by level:
    [10 14 20]
    [1 5] [12 13] [15] [25 30]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rb.py

Each insertion prints its repair, and a check of the rules runs after every one. Key 13 needed both kinds: a recolouring that moved the problem up, then a double rotation. The ten keys cost 5 rotations and ended at height 3, with two black nodes on every path and the 2-3-4 root [10 14 20] above four leaves, as in the diagram.

B-trees: hundreds of keys per node

A B-tree of order m keeps up to m − 1 sorted keys and m children in each node, every node but the root at least half full, and every leaf at the same depth (Rudolf Bayer and Edward McCreight, 1972). A search reads one node per level and searches the sorted keys inside it. An insertion puts the key into a leaf; a leaf that overflows splits in two and pushes its middle key up into its parent, which may split in turn. When the root splits, a new root appears above it, so the tree grows at the top and its leaves stay level. A B-tree of order 4 is exactly a 2-3-4 tree:

A B-tree of order 4 built from 20 keys, splits printed Python · btree/btree.py
"""A B-tree of order 4 (at most 3 keys and 4 children per node) built from 20 keys, with every split printed."""

import random


class BNode:
    def __init__(self, keys=None, children=None):
        self.keys = keys or []
        self.children = children or []  # empty for a leaf


def insert(node, key, log, order):
    """Inserts key below node; returns (median, new right sibling) when node splits, else None."""
    i = sum(k < key for k in node.keys)  # the child (or slot) to the right of every smaller key
    if i < len(node.keys) and node.keys[i] == key:
        return None  # keys are unique
    if not node.children:
        node.keys.insert(i, key)
    else:
        split = insert(node.children[i], key, log, order)
        if split:
            median, right = split
            node.keys.insert(i, median)
            node.children.insert(i + 1, right)
    if len(node.keys) < order:
        return None
    mid = len(node.keys) // 2  # an overfull node holds `order` keys: keep the left half, send the middle key up
    median = node.keys[mid]
    right = BNode(node.keys[mid + 1 :], node.children[mid + 1 :])
    log.append(f"[{' '.join(map(str, node.keys))}] splits, {median} moves up")
    node.keys, node.children = node.keys[:mid], node.children[: mid + 1]
    return median, right


def add(root, key, log, order=4):
    split = insert(root, key, log, order)
    if split:  # the root itself split: a new root makes the tree one level taller
        median, right = split
        root = BNode([median], [root, right])
        log.append(f"new root [{median}]")
    return root


def levels(root):
    rows, level = [], [root]
    while level:
        rows.append(" ".join("[" + " ".join(map(str, n.keys)) + "]" for n in level))
        level = [c for n in level for c in n.children]
    return rows


def build(keys, order=4):
    root = BNode()
    for key in keys:
        root = add(root, key, [], order)
    return root


if __name__ == "__main__":
    keys = list(range(1, 21))
    random.Random(4).shuffle(keys)
    root = BNode()
    for key in keys:
        log = []
        root = add(root, key, log)
        if log:
            print(f"insert {key:2}: {'; '.join(log)}")
    print(f"{len(keys)} keys inserted in the order {keys}")
    for depth, row in enumerate(levels(root)):
        print(f"level {depth}: {row}")

Output

insert 15: [11 15 18 20] splits, 18 moves up; new root [18]
insert 17: [6 11 15 17] splits, 15 moves up
insert  5: [5 6 11 12] splits, 11 moves up
insert  7: [5 6 7 9] splits, 7 moves up; [7 11 15 18] splits, 15 moves up; new root [15]
insert  2: [1 2 5 6] splits, 5 moves up
insert  4: [1 2 3 4] splits, 3 moves up; [3 5 7 11] splits, 7 moves up
20 keys inserted in the order [18, 20, 11, 15, 6, 19, 17, 12, 5, 9, 7, 1, 14, 2, 3, 16, 13, 4, 10, 8]
level 0: [7 15]
level 1: [3 5] [11] [18]
level 2: [1 2] [4] [6] [8 9 10] [12 13 14] [16 17] [19 20]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 btree.py

Six of the twenty insertions split a node; two of them split all the way up and gave the tree a new root.

Why databases choose B-trees: page reads

On disk, on an SSD and even in memory, a lookup costs roughly one read or cache miss per node it touches. A node sized to a 4 KB page holds about 256 keys and child pointers, so the tree branches 256 ways:

Nodes one lookup visits: B-trees against binary trees Python · btree/page_reads.py
"""How many nodes a lookup visits: a B-tree with 4 KB pages against binary trees, for 10^3 to 10^9 keys."""

import math

PAGE, ENTRY = 4096, 16  # bytes per page; one entry = an 8-byte key and an 8-byte page number
fanout = PAGE // ENTRY  # children per full node: 256


def btree_levels(n, children):
    """Levels of a B-tree whose nodes are all full (the best case) and half full (the worst a B-tree allows)."""
    return math.ceil(math.log(n + 1, children))


print(f"page {PAGE} bytes, entry {ENTRY} bytes: up to {fanout} children per node, at least {fanout // 2}")
print("nodes one lookup visits, at most:")
print(f"{'keys':>15} {'perfect binary':>15} {'red-black worst':>16} {'B-tree, full':>13} {'B-tree, half full':>18}")
for n in (10**3, 10**6, 10**9):
    perfect = math.ceil(math.log2(n + 1))
    rb_worst = math.floor(2 * math.log2(n + 1))
    print(f"{n:>15,} {perfect:>15} {rb_worst:>16} {btree_levels(n, fanout):>13} {btree_levels(n, fanout // 2):>18}")

Output

page 4096 bytes, entry 16 bytes: up to 256 children per node, at least 128
nodes one lookup visits, at most:
           keys  perfect binary  red-black worst  B-tree, full  B-tree, half full
          1,000              10               19             2                  2
      1,000,000              20               39             3                  3
  1,000,000,000              30               59             4                  5

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 page_reads.py

A billion keys fit in 4 levels of full nodes and 5 of half-full ones, where a red-black tree may need 59 nodes on one path. That is why PostgreSQL creates B-tree indexes by default and SQLite stores every table and index as a B-tree.

AVL trees, red-black trees and B-trees compared

Three balanced search trees
Criterion AVL treeRed-black treeB-tree of order m
Keys per node 11up to m − 1
Height for n keys below about 1.44 log₂ nat most 2 log₂(n + 1)about log base m/2 of n
Repair after an insertion one single or double rotationrecolourings, at most 2 rotationssplits up the path
Typical home in-memory indexes with many lookupslanguage libraries: TreeMap, std::mapdatabase and file-system indexes
When to choose Lookups far outnumber updatesGeneral-purpose ordered maps in memoryData on pages: disk, SSD or cache lines

Red-black trees are the usual default for ordered maps in memory, and B-trees win whenever reading a node is the expensive step.

Exercise

Exercise · Medium · Python, JavaScript

Check every red-black rule and return the black-height

A coloured binary tree is made of Node(key, color, left, right) objects, where color is "red" or "black" and an empty subtree is None (JavaScript: null), which counts as a black leaf. Write black_height(root) (JavaScript: blackHeight(root)). For a valid red-black tree it returns the number of black nodes on every path from the root down to an empty subtree (an empty tree has 0). Otherwise it raises ValueError (JavaScript: throws an Error) whose message is the name of the rule that is broken:

  • "colour": a node's colour is neither "red" nor "black";
  • "root": the root is red;
  • "order": a key is not strictly between the keys above it that bound it, so the tree is not a search tree;
  • "red-red": a red node has a red child;
  • "black-height": two paths from some node down to empty subtrees hold different numbers of black nodes.

The tests break one rule at a time, so your checks may run in any order. The function must take O(n) time: one pass over the tree, with no recomputation of a subtree's black count.

Python · Starter code · rb_check.py

class Node:
    def __init__(self, key, color, left=None, right=None):
        self.key = key
        self.color = color  # "red" or "black"
        self.left = left
        self.right = right


def black_height(root):
    """Black nodes on every root-to-empty path of a valid red-black tree; raises ValueError naming a broken rule."""
    raise NotImplementedError("write black_height")
The sample tests · test_rb_check.py
from rb_check import Node, black_height


def B(key, left=None, right=None):
    return Node(key, "black", left, right)


def R(key, left=None, right=None):
    return Node(key, "red", left, right)


def broken_rule(root):
    try:
        black_height(root)
    except ValueError as e:
        return str(e)
    return None


def perfect_black(lo, hi):
    """A perfect all-black tree on the keys lo..hi (a valid red-black tree)."""
    if lo > hi:
        return None
    mid = (lo + hi) // 2
    return B(mid, perfect_black(lo, mid - 1), perfect_black(mid + 1, hi))


def test_small_valid_trees():
    """an empty tree has black-height 0, a black root 1, and the 10-key tree of the lesson 2"""
    assert black_height(None) == 0
    assert black_height(B(7)) == 1
    lesson = B(14, R(10, B(5, R(1)), B(12, None, R(13))), R(20, B(15), B(30, R(25))))
    assert black_height(lesson) == 2


def test_large_valid_tree():
    """a perfect black tree of 1,023 keys has black-height 10"""
    assert black_height(perfect_black(1, 1023)) == 10


def test_root_and_colour():
    """a red root breaks "root", and a colour other than red or black breaks "colour\""""
    assert broken_rule(R(7)) == "root"
    assert broken_rule(B(7, Node(3, "pink"))) == "colour"


def test_order_uses_bounds():
    """12 below the left child of 10 breaks "order", even though it is larger than its own parent 5"""
    assert broken_rule(B(10, B(5, None, R(3)), B(15))) == "order"
    assert broken_rule(B(10, B(5, None, R(12)), B(15))) == "order"


def test_red_red_and_black_height():
    """two reds in a row break "red-red"; an extra black node on one side breaks "black-height\""""
    assert broken_rule(B(10, R(5, R(3)), R(15))) == "red-red"
    assert broken_rule(B(10, B(5, B(3)), B(15))) == "black-height"

JavaScript · Starter code · rb_check.mjs

export class Node {
  constructor(key, color, left = null, right = null) {
    this.key = key;
    this.color = color; // "red" or "black"
    this.left = left;
    this.right = right;
  }
}

/** Black nodes on every root-to-empty path of a valid red-black tree; throws an Error naming a broken rule. */
export function blackHeight(root) {
  throw new Error('write blackHeight');
}
The sample tests · rb_check.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { Node, blackHeight } from './rb_check.mjs';

const B = (key, left = null, right = null) => new Node(key, 'black', left, right);
const R = (key, left = null, right = null) => new Node(key, 'red', left, right);

function brokenRule(root) {
  try {
    blackHeight(root);
  } catch (e) {
    return e.message;
  }
  return null;
}

function perfectBlack(lo, hi) {
  if (lo > hi) return null;
  const mid = Math.floor((lo + hi) / 2);
  return B(mid, perfectBlack(lo, mid - 1), perfectBlack(mid + 1, hi));
}

test('an empty tree has black-height 0, a black root 1, and the 10-key tree of the lesson 2', () => {
  assert.equal(blackHeight(null), 0);
  assert.equal(blackHeight(B(7)), 1);
  const lesson = B(14, R(10, B(5, R(1)), B(12, null, R(13))), R(20, B(15), B(30, R(25))));
  assert.equal(blackHeight(lesson), 2);
});

test('a perfect black tree of 1,023 keys has black-height 10', () => {
  assert.equal(blackHeight(perfectBlack(1, 1023)), 10);
});

test('a red root breaks "root", and a colour other than red or black breaks "colour"', () => {
  assert.equal(brokenRule(R(7)), 'root');
  assert.equal(brokenRule(B(7, new Node(3, 'pink'))), 'colour');
});

test('12 below the left child of 10 breaks "order", even though it is larger than its own parent 5', () => {
  assert.equal(brokenRule(B(10, B(5, null, R(3)), B(15))), 'order');
  assert.equal(brokenRule(B(10, B(5, null, R(12)), B(15))), 'order');
});

test('two reds in a row break "red-red"; an extra black node on one side breaks "black-height"', () => {
  assert.equal(brokenRule(B(10, R(5, R(3)), R(15))), 'red-red');
  assert.equal(brokenRule(B(10, B(5, B(3)), B(15))), 'black-height');
});
A hint

Write one recursive helper that takes a node and the open interval (low, high) its key must lie in, and returns the black count of its subtree: 0 for None, and otherwise the common count of its two children plus one if the node is black. Raise "black-height" as soon as the two children's counts differ, and pass (low, key) to the left child and (key, high) to the right one.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 Which of these is not one of the red-black rules?

    Choose one answer.

    Show the answer to question 1

    Answer: The two subtrees of every node differ in height by at most one

    The height rule belongs to AVL trees. A red-black tree may be much less even: one subtree can be up to twice as tall as its sibling, because only the black nodes are balanced and red nodes can sit between them.

  2. Question 2 of 5 In the 2-3-4 view, what does a black node with two red children become?

    Choose one answer.

    Show the answer to question 2

    Answer: One node with three keys and four children

    Each red node is merged into its black parent. A black node with two red children therefore holds its own key and both of theirs, and the four subtrees below the red children become its four children, like the root [10 14 20] in the lesson's run.

  3. Question 3 of 5 In the recorded run, how many black nodes lie on every path from the root down to an empty subtree of the 10-key tree?

    Type a number.

    Show the answer to question 3

    Answer: 2

    The run's summary line says 2, and its check confirms the same count on every path. The 2-3-4 tree it encodes has two levels, one per black node on a path.

  4. Question 4 of 5 In the page-read table, how many levels does a B-tree whose 4 KB nodes branch 256 ways need for a billion keys when every node is full?

    Type a number.

    Show the answer to question 4

    Answer: 4

    256⁴ is about 4.3 billion, so four levels hold a billion keys, and five are needed when nodes are only half full. A red-black tree with the same keys may need up to 59 nodes on one path.

  5. Question 5 of 5 Why do databases keep their indexes in B-trees rather than in red-black trees?

    Choose one answer.

    Show the answer to question 5

    Answer: A node fills a whole page, so a lookup reads only a few pages

    Each node visit costs a page read or a cache miss, so what matters is how many nodes a lookup touches. Hundreds of keys per node keep the tree three to five levels deep, where a binary tree needs 30 or more.

The height proof and the deletion cases, worked and stress-tested Pro: opens with a Pro, Premium or Ultimate pass

The 2 log₂(n + 1) bound follows from counting black nodes, and deletion is where red-black code gets hard. This part proves the bound, maps every deletion case to a borrow or a merge in the 2-3-4 tree, and checks the code on 50,000 random operations.

In this part:

  • Why black nodes bound the height: the proof
  • Deletion: the four repair cases as borrows and merges

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Red-black trees in five languages, and against AVL trees, measured Pro: opens with a Pro, Premium or Ultimate pass

The insertion code is the same in every language; what changes is how nodes are owned and how the empty leaf is represented. This part runs it in JavaScript, Java, C++ and Go, then measures red-black and AVL trees on the same keys.

In this part:

  • The same insertion in JavaScript, Java, C++ and Go
  • AVL or red-black: heights, depths and rotations measured
  • What the libraries promise

3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

B-tree deletion, B+ tree fan-out and SQLite's B-trees, measured Pro: opens with a Pro, Premium or Ultimate pass

Deletion mirrors insertion: a node that falls below half full borrows a key from a sibling or merges with one, and the tree can lose a level at the root. This part works through deletion, puts numbers on B+ trees, and measures SQLite’s own B-trees for a million rows.

In this part:

  • Deletion: borrow, merge, and a root that disappears
  • B+ trees: records in the leaves, worked numbers
  • SQLite’s B-trees for a million rows, measured
  • PostgreSQL’s default index

3 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher · Warm-up

    What does the red-black colouring guarantee about a tree's height, and why?

    A red-black tree with n keys is at most 2 log₂(n + 1) nodes tall, so search, insertion and deletion take O(log n) time in the worst case. Two rules give the guarantee. Every path from a node down to an empty subtree passes the same number of black nodes, so the black nodes alone form a perfectly balanced skeleton. And a red node never has a red child, so at most every second node on a path is red. Together they mean that the longest path from the root is at most twice as long as the shortest one. The tree is therefore less evenly balanced than an AVL tree, but it needs fewer rotations to stay that way: at most two per insertion and three per deletion.

  2. Mid to senior

    An in-memory index of one million keys serves 50 lookups for every insert or delete. A colleague insists on an AVL tree because it is shorter than a red-black tree. How much shorter is it, does that matter for lookups, and which would you choose?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A table holds one billion rows with 8-byte keys, indexed by a B-tree on 8 KB pages. Estimate the index's height, the page reads for a point lookup and for a range of 10,000 consecutive keys, and compare with a red-black tree stored on disk.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A Java service swapped HashMap for TreeMap to get its keys in sorted order, and p99 latency rose. With one million keys, estimate how much more work a lookup now does, and propose ways to keep sorted iteration without paying for it on every request.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A red-black tree balances only its black nodes and allows at most one red between two blacks, so its height is at most 2 log₂(n + 1).
  • Merging each red node into its black parent turns a red-black tree into a 2-3-4 tree with every leaf at one depth.
  • B-trees put hundreds of keys in a node sized to a page, so a lookup reads only three to five nodes.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.