← Data structures & algorithms
Trees Keep keys in order as they come and go

Binary search tree

Find a badge by name, and keep the tree short.

Every CREATE INDEX you have run in PostgreSQL built a B-tree unless you asked for something else, and its documentation says why: B-trees “can handle equality and range queries on data that can be sorted into some ordering.” A B-tree is the wide, disk-friendly cousin of the tree in this lesson, and Java’s TreeMap, “a Red-Black tree based” map, is a close relative. Let’s run a conference registration desk on one: import the sorted export, watch a plain tree turn into one long branch, and keep it short with rotations.

TypeScriptGoOne badge desk, two implementations.

01 / The idea

Find a name in a few comparisons.

A conference desk has a badge for every registered attendee. Someone walks up and says their name; staff need that badge now, walk-ins register all morning, badges leave as people collect them, and at any moment the desk may want the next dozen names in order to lay out.

A binary search tree keeps every name smaller than a node on its left and every larger name on its right. To find a name, start at the top and go left or right until it matches. To add one, go the same way until there is nowhere to go, and attach it there. Reading the left side, then the node, then the right side lists every name in order.

The idea dates to 1960, found independently by several people. The kernel’s rbtree notes list its users: I/O schedulers, high-resolution timers, “Virtual memory areas (VMAs) are tracked with red-black trees, as are epoll file descriptors.” C++ maps are “typically implemented as binary search trees,” and TreeMap “provides guaranteed log(n) time cost” for its lookups and changes.

02 / Name the rule

Smaller left, larger right, and keep it short.

A search costs one comparison per level, so the tree’s height is the price of every call. Nine names can sit in four levels. But names that arrive in order each go right of everything before them, and nine names make nine levels: a list on its side. A registration export sorted by family name does exactly that.

A balanced tree refuses to let that happen. After each change it walks back up, and wherever one side of a node is two levels taller than the other, it rotates: the taller child moves up, the node moves down on the other side, and one subtree changes parent. Names stay in order; the height shrinks by one.

This lesson uses the first such rule, the AVL tree, published by Georgy Adelson-Velsky and Evgenii Landis in 1962: at every node, the two sides differ in height by at most one. That keeps a tree of n names within about 1.44 log₂ n levels.

Why do libraries use red-black trees instead?Looser balance, fewer rotations

A red-black tree colors each node and keeps a weaker promise: no path is more than twice as long as another. The kernel’s notes put the trade plainly: red-black trees “provide faster real-time bounded worst case performance for insertion and deletion (at most two rotations and three rotations, respectively, to balance the tree), with slightly slower (but still O(log n)) lookup time.”

AVL keeps the stricter promise, which makes lookups a little shorter and changes a little busier. Both keep every call to O(log n); the rotations are the same moves. AVL’s rule is simpler to see, which is why it is the one here.

03 / Follow one operation

Nine names, in export order.

The export lists nine attendees alphabetically, from Chidi Abara to Sora Ito. The animation imports them into a plain tree, then into a balanced one, and runs the desk: a search, a walk-in, a pickup, and the next badges.

Before you watch, predict how tall each tree ends, and who takes Camille Dubois’s place when she collects her badge from the top of the balanced tree. The animation replays what the TypeScript example recorded. Try it lets you run the desk yourself.

Binary search tree

Go left or right. Keep it short.

Search tree · L holds smaller names, R larger; h is the height below and including a name

Plain tree · 9 names imported in export order

  • Abara, Chidih9
    • RBergström, Linneah8
      • RCastillo, Mateoh7
        • RDubois, Camilleh6
          • REndo, Harukih5
            • RFischer, Jonash4
              • RGupta, Priyah3
                • RHaddad, Laylah2
                  • RIto, Sorah1

Waiting 9 Height 9 Compared 0 Rotations 0

01/ 08
Nine names, one branch

One long branch, nine tall.

The export arrives sorted, so every name is larger than all the names before it and goes to the right of them. The plain tree is a list on its side: height 9.

Reduced motion: choose a scene to see its completed state.

Read this scene

The export arrives sorted, so every name is larger than all the names before it and goes to the right of them. The plain tree is a list on its side: height 9.

The export arrives sorted, so every name is larger than all the names before it and goes to the right of them. The plain tree is a list on its side: height 9.

Waiting: 9. Compared so far: 0. Rotations so far: 0.

Watch restarts when you return. Step through keeps your selected step. Try it starts from the imported export each time you open it.

04 / Read the shape

One tree, one badge desk.

Basic form is SearchTree: insert, find, remove, and entries in key order, balanced by default and able to switch balancing off so the lesson can measure both. In the wild wraps it in BadgeDesk, which checks names, files badges by family then given name, refuses a second registration, and lists the next badges to lay out.

A search tree over string keys that balances itself with AVL rotations, or not, so the two can be compared. Insert, find, remove with a successor, and read entries in key order, recording every comparison, attachment, rotation, and removal.

TypeScriptReading
badges.ts
export type Step = {
	/**
	 * left or right: the key is smaller or larger than this node's, so go that way. found: the
	 * keys match. attach: add the key as a node under `other`, or as the root. rotate-left or
	 * rotate-right: turn the subtree at this node to restore balance. detach: take out a node
	 * with at most one child, which moves up. successor: a node with two children takes the key
	 * of `other`, the smallest key to its right, detached just before. visit: read a node in
	 * key order.
	 */
	kind:
		| 'left'
		| 'right'
		| 'found'
		| 'attach'
		| 'rotate-left'
		| 'rotate-right'
		| 'detach'
		| 'successor'
		| 'visit';
	at: string;
	other?: string;
};

export type Entry<V> = { key: string; value: V };
export type Outline = { key: string; left: Outline | null; right: Outline | null };

type Node<V> = {
	key: string;
	value: V;
	left: Node<V> | null;
	right: Node<V> | null;
	height: number;
};
type Link<V> = { node: Node<V>; side: 'left' | 'right' };

const heightOf = <V>(node: Node<V> | null) => node?.height ?? 0;
const update = <V>(node: Node<V>) => {
	node.height = 1 + Math.max(heightOf(node.left), heightOf(node.right));
};

/** Compare by code point, as Go compares strings, not by UTF-16 unit as `<` does. */
export function compareKeys(a: string, b: string): number {
	for (let i = 0; i < Math.min(a.length, b.length); i++) {
		const x = a.charCodeAt(i);
		const y = b.charCodeAt(i);
		if (x === y) continue;
		// A surrogate unit belongs to a code point above U+FFFF, which sorts after every other unit.
		const xHigh = x >= 0xd800 && x <= 0xdfff;
		const yHigh = y >= 0xd800 && y <= 0xdfff;
		return xHigh === yHigh ? x - y : xHigh ? 1 : -1;
	}
	return a.length - b.length;
}

// A binary search tree keeps every key smaller than a node on its left and every larger key
// on its right, so a search follows one path down. The path is as long as the tree is tall.
// Keys that arrive in order make one long branch, so a balanced tree measures the heights of
// each node's two sides and rotates whenever they differ by more than one (an AVL tree).
export class SearchTree<V> {
	#root: Node<V> | null = null;
	#size = 0;
	readonly balanced: boolean;

	constructor({ balanced = true }: { balanced?: boolean } = {}) {
		this.balanced = balanced;
	}

	// Returns false, and changes nothing, when the key is already there.
	insert(key: string, value: V, steps?: Step[]): boolean {
		const path: Link<V>[] = [];
		for (let node = this.#root; node;) {
			const order = compareKeys(key, node.key);
			if (order === 0) {
				steps?.push({ kind: 'found', at: node.key });
				return false;
			}
			const side = order < 0 ? 'left' : 'right';
			steps?.push({ kind: side, at: node.key });
			path.push({ node, side });
			node = node[side];
		}
		const fresh: Node<V> = { key, value, left: null, right: null, height: 1 };
		const parent = path.at(-1);
		if (parent) {
			steps?.push({ kind: 'attach', at: key, other: parent.node.key });
			parent.node[parent.side] = fresh;
		} else {
			steps?.push({ kind: 'attach', at: key });
			this.#root = fresh;
		}
		this.#size++;
		this.#fix(path, steps);
		return true;
	}

	find(key: string, steps?: Step[]): V | undefined {
		for (let node = this.#root; node;) {
			const order = compareKeys(key, node.key);
			if (order === 0) {
				steps?.push({ kind: 'found', at: node.key });
				return node.value;
			}
			const side = order < 0 ? 'left' : 'right';
			steps?.push({ kind: side, at: node.key });
			node = node[side];
		}
		return undefined;
	}

	// Returns false when the key is not there.
	remove(key: string, steps?: Step[]): boolean {
		const path: Link<V>[] = [];
		let node = this.#root;
		while (node && compareKeys(key, node.key) !== 0) {
			const side = compareKeys(key, node.key) < 0 ? 'left' : 'right';
			steps?.push({ kind: side, at: node.key });
			path.push({ node, side });
			node = node[side];
		}
		if (!node) return false;
		steps?.push({ kind: 'found', at: node.key });
		if (node.left && node.right) {
			// Two children: the smallest key on the right moves into this node.
			steps?.push({ kind: 'right', at: node.key });
			path.push({ node, side: 'right' });
			let next = node.right;
			while (next.left) {
				steps?.push({ kind: 'left', at: next.key });
				path.push({ node: next, side: 'left' });
				next = next.left;
			}
			steps?.push({ kind: 'detach', at: next.key });
			const above = path.at(-1)!;
			above.node[above.side] = next.right;
			steps?.push({ kind: 'successor', at: node.key, other: next.key });
			node.key = next.key;
			node.value = next.value;
		} else {
			steps?.push({ kind: 'detach', at: node.key });
			const child = node.left ?? node.right;
			const parent = path.at(-1);
			if (parent) parent.node[parent.side] = child;
			else this.#root = child;
		}
		this.#size--;
		this.#fix(path, steps);
		return true;
	}

	/** Up to `limit` entries in key order. */
	entries(limit: number, steps?: Step[]): Entry<V>[] {
		if (!Number.isInteger(limit) || limit < 1 || limit > 100_000)
			throw new RangeError('limit must be a whole number from 1 to 100000');
		const found: Entry<V>[] = [];
		const stack: Node<V>[] = [];
		let node = this.#root;
		while ((node || stack.length) && found.length < limit) {
			for (; node; node = node.left) stack.push(node);
			node = stack.pop()!;
			steps?.push({ kind: 'visit', at: node.key });
			found.push({ key: node.key, value: node.value });
			node = node.right;
		}
		return found;
	}

	/** The tree's shape, for drawing. Copied with an explicit stack, like every other walk. */
	outline(): Outline | null {
		if (!this.#root) return null;
		const top: Outline = { key: this.#root.key, left: null, right: null };
		const pending: [Node<V>, Outline][] = [[this.#root, top]];
		while (pending.length) {
			const [node, copy] = pending.pop()!;
			if (node.left) {
				copy.left = { key: node.left.key, left: null, right: null };
				pending.push([node.left, copy.left]);
			}
			if (node.right) {
				copy.right = { key: node.right.key, left: null, right: null };
				pending.push([node.right, copy.right]);
			}
		}
		return top;
	}

	get size(): number {
		return this.#size;
	}

	/** Nodes on the longest path from the root; 0 when empty. */
	get height(): number {
		return heightOf(this.#root);
	}

	// Walk back up the path, updating heights and, when balancing, rotating any node whose
	// sides differ in height by more than one.
	#fix(path: Link<V>[], steps?: Step[]) {
		for (let i = path.length - 1; i >= 0; i--) {
			const fixed = this.#rebalance(path[i].node, steps);
			if (i === 0) this.#root = fixed;
			else path[i - 1].node[path[i - 1].side] = fixed;
		}
	}

	#rebalance(node: Node<V>, steps?: Step[]): Node<V> {
		update(node);
		if (!this.balanced) return node;
		const lean = heightOf(node.left) - heightOf(node.right);
		if (lean > 1) {
			if (heightOf(node.left!.left) < heightOf(node.left!.right))
				node.left = this.#rotateLeft(node.left!, steps);
			return this.#rotateRight(node, steps);
		}
		if (lean < -1) {
			if (heightOf(node.right!.right) < heightOf(node.right!.left))
				node.right = this.#rotateRight(node.right!, steps);
			return this.#rotateLeft(node, steps);
		}
		return node;
	}

	#rotateRight(node: Node<V>, steps?: Step[]): Node<V> {
		steps?.push({ kind: 'rotate-right', at: node.key });
		const up = node.left!;
		node.left = up.right;
		up.right = node;
		update(node);
		update(up);
		return up;
	}

	#rotateLeft(node: Node<V>, steps?: Step[]): Node<V> {
		steps?.push({ kind: 'rotate-left', at: node.key });
		const up = node.right!;
		node.right = up.left;
		up.left = node;
		update(node);
		update(up);
		return up;
	}
}
GoAlongside
badges.go
// Step is one move. Kind is "left" or "right" (the key is smaller or larger than this
// node's, so go that way), "found" (the keys match), "attach" (add the key as a node under
// Other, or as the root), "rotate-left" or "rotate-right" (turn the subtree at this node to
// restore balance), "detach" (take out a node with at most one child, which moves up),
// "successor" (a node with two children takes the key of Other, the smallest key to its
// right, detached just before), or "visit" (read a node in key order).
type Step struct {
	Kind  string `json:"kind"`
	At    string `json:"at"`
	Other string `json:"other,omitempty"`
}

type Entry[V any] struct {
	Key   string
	Value V
}

// Outline is the tree's shape, for drawing.
type Outline struct {
	Key         string
	Left, Right *Outline
}

type node[V any] struct {
	key         string
	value       V
	left, right *node[V]
	height      int
}

func heightOf[V any](n *node[V]) int {
	if n == nil {
		return 0
	}
	return n.height
}

func (n *node[V]) update() { n.height = 1 + max(heightOf(n.left), heightOf(n.right)) }

// link is a node on the way down and the side taken from it.
type link[V any] struct {
	node *node[V]
	left bool
}

func (l link[V]) set(child *node[V]) {
	if l.left {
		l.node.left = child
	} else {
		l.node.right = child
	}
}

func record(steps *[]Step, kind, at, other string) {
	if steps != nil {
		*steps = append(*steps, Step{kind, at, other})
	}
}

// Tree is a binary search tree: every key smaller than a node is on its left and every
// larger key on its right, so a search follows one path down, as long as the tree is tall.
// Keys that arrive in order make one long branch, so a balanced tree measures the heights of
// each node's two sides and rotates whenever they differ by more than one (an AVL tree).
type Tree[V any] struct {
	root     *node[V]
	size     int
	balanced bool
}

func NewTree[V any](balanced bool) *Tree[V] { return &Tree[V]{balanced: balanced} }

// Insert returns false, and changes nothing, when the key is already there.
func (t *Tree[V]) Insert(key string, value V, steps *[]Step) bool {
	var path []link[V]
	for n := t.root; n != nil; {
		switch c := strings.Compare(key, n.key); {
		case c == 0:
			record(steps, "found", n.key, "")
			return false
		case c < 0:
			record(steps, "left", n.key, "")
			path = append(path, link[V]{n, true})
			n = n.left
		default:
			record(steps, "right", n.key, "")
			path = append(path, link[V]{n, false})
			n = n.right
		}
	}
	fresh := &node[V]{key: key, value: value, height: 1}
	if len(path) == 0 {
		record(steps, "attach", key, "")
		t.root = fresh
	} else {
		parent := path[len(path)-1]
		record(steps, "attach", key, parent.node.key)
		parent.set(fresh)
	}
	t.size++
	t.fix(path, steps)
	return true
}

func (t *Tree[V]) Find(key string, steps *[]Step) (V, bool) {
	for n := t.root; n != nil; {
		switch c := strings.Compare(key, n.key); {
		case c == 0:
			record(steps, "found", n.key, "")
			return n.value, true
		case c < 0:
			record(steps, "left", n.key, "")
			n = n.left
		default:
			record(steps, "right", n.key, "")
			n = n.right
		}
	}
	var zero V
	return zero, false
}

// Remove returns false when the key is not there.
func (t *Tree[V]) Remove(key string, steps *[]Step) bool {
	var path []link[V]
	n := t.root
	for n != nil && n.key != key {
		if key < n.key {
			record(steps, "left", n.key, "")
			path = append(path, link[V]{n, true})
			n = n.left
		} else {
			record(steps, "right", n.key, "")
			path = append(path, link[V]{n, false})
			n = n.right
		}
	}
	if n == nil {
		return false
	}
	record(steps, "found", n.key, "")
	if n.left != nil && n.right != nil {
		// Two children: the smallest key on the right moves into this node.
		record(steps, "right", n.key, "")
		path = append(path, link[V]{n, false})
		next := n.right
		for next.left != nil {
			record(steps, "left", next.key, "")
			path = append(path, link[V]{next, true})
			next = next.left
		}
		record(steps, "detach", next.key, "")
		path[len(path)-1].set(next.right)
		record(steps, "successor", n.key, next.key)
		n.key, n.value = next.key, next.value
	} else {
		record(steps, "detach", n.key, "")
		child := n.left
		if child == nil {
			child = n.right
		}
		if len(path) == 0 {
			t.root = child
		} else {
			path[len(path)-1].set(child)
		}
	}
	t.size--
	t.fix(path, steps)
	return true
}

// Entries returns up to limit entries in key order.
func (t *Tree[V]) Entries(limit int, steps *[]Step) ([]Entry[V], error) {
	if limit < 1 || limit > 100_000 {
		return nil, errors.New("limit must be a whole number from 1 to 100000")
	}
	found := []Entry[V]{}
	var stack []*node[V]
	n := t.root
	for (n != nil || len(stack) > 0) && len(found) < limit {
		for ; n != nil; n = n.left {
			stack = append(stack, n)
		}
		n = stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		record(steps, "visit", n.key, "")
		found = append(found, Entry[V]{n.key, n.value})
		n = n.right
	}
	return found, nil
}

func (t *Tree[V]) Outline() *Outline {
	var copyOf func(n *node[V]) *Outline
	copyOf = func(n *node[V]) *Outline {
		if n == nil {
			return nil
		}
		return &Outline{n.key, copyOf(n.left), copyOf(n.right)}
	}
	return copyOf(t.root)
}

func (t *Tree[V]) Size() int { return t.size }

// Height counts nodes on the longest path from the root; 0 when empty.
func (t *Tree[V]) Height() int { return heightOf(t.root) }

// fix walks back up the path, updating heights and, when balancing, rotating any node whose
// sides differ in height by more than one.
func (t *Tree[V]) fix(path []link[V], steps *[]Step) {
	for i := len(path) - 1; i >= 0; i-- {
		fixed := t.rebalance(path[i].node, steps)
		if i == 0 {
			t.root = fixed
		} else {
			path[i-1].set(fixed)
		}
	}
}

func (t *Tree[V]) rebalance(n *node[V], steps *[]Step) *node[V] {
	n.update()
	if !t.balanced {
		return n
	}
	switch lean := heightOf(n.left) - heightOf(n.right); {
	case lean > 1:
		if heightOf(n.left.left) < heightOf(n.left.right) {
			n.left = rotateLeft(n.left, steps)
		}
		return rotateRight(n, steps)
	case lean < -1:
		if heightOf(n.right.right) < heightOf(n.right.left) {
			n.right = rotateRight(n.right, steps)
		}
		return rotateLeft(n, steps)
	}
	return n
}

func rotateRight[V any](n *node[V], steps *[]Step) *node[V] {
	record(steps, "rotate-right", n.key, "")
	up := n.left
	n.left, up.right = up.right, n
	n.update()
	up.update()
	return up
}

func rotateLeft[V any](n *node[V], steps *[]Step) *node[V] {
	record(steps, "rotate-left", n.key, "")
	up := n.right
	n.right, up.left = up.left, n
	n.update()
	up.update()
	return up
}
Reading the TypeScriptA path array, not recursion

insert and remove push each node and the side taken onto a path, then #fix walks it backward, updating heights and rotating. Recursion would be shorter, but a plain tree of a 10,000-name export is 10,000 deep, deeper than a JavaScript stack allows.

compareKeys compares by code point. JavaScript’s < compares UTF-16 units, which puts some characters above U+FFFF before others below it, and would order names differently from Go.

Reading the GoGenerics and a link type

Tree[V any] holds values of any type, and link records a node and whether the path went left, with a set method that relinks that side. strings.Compare compares bytes, and UTF-8 bytes sort in code point order, so no helper is needed.

Find returns the value and a boolean, and errors are values: Register returns error for a bad name or a second registration.

What would I normally use in application code?Often not a hand-written tree

Neither TypeScript nor Go ships a sorted map. For a list that rarely changes, a sorted array with binary search finds a name in the same number of comparisons with far less memory. A database index is a B-tree, and so is Rust’s BTreeMap, whose documentation explains that a binary search tree allocates every node separately, and “every comparison is a potential cache-miss.”

Reach for a balanced tree when keys change often and you also need them in order: Java’s TreeMap, C++’s std::map, or a well-tested package. Writing one yourself is for learning, or for a structure the libraries do not offer.

05 / Try a decision

A sorted export, all day.

The desk is slow and the cause is the export’s order. Decide which fix survives a full day, noon’s day passes included, before the feedback tells you.

A conference of 2,000 imports its registration export, sorted by family name, into a plain search tree, and the last names take 2,000 comparisons to find. The desk stays open all day, and at noon it sells 300 day passes, registered in order as Guest 001 to Guest 300. What do you change?

06 / Follow the cost

2,000 comparisons, or 11.

Here is every operation at a glance, with n waiting badges and a tree of height h. The rest of this section measures a conference of 2,000.

Badge desk: time and extra space
OperationTimeExtra spaceWhat it assumes
Find a nameO(h)O(1)h is the height: about log₂ n when balanced, up to n when not.
Register a nameO(h)O(h)Walk to an empty place and attach. A balanced tree needs at most one single or double rotation.
Pick up a badgeO(h)O(h)With two children, the smallest name on the right takes the place. Balancing may rotate at each level.
Next k badges in orderO(h + k)O(h)Walk left to the smallest name, then read in order, keeping a stack of the way back.
Plain tree, sorted exportO(n) per callO(n)Every name goes right of the last, so the height is n.
Sorted array insteadO(log n) find, O(n) insertO(n)Binary search finds a name; a walk-in in the middle shifts every name after it.
Hold n waiting badges—O(n)One node per badge: the name key, the badge, two child links, and a stored height.

In the animation, nine names in export order made a plain tree nine tall and a balanced tree four tall, and finding the last name took nine comparisons against four. At 2,000 names in export order, the plain tree was 2,000 tall: importing took 1,999,000 comparisons, and looking up every attendee once averaged 1,000.5 comparisons each. The balanced tree was 11 tall: the import took 19,953 comparisons and 1,989 rotations, and a lookup averaged 9.98.

Random order is kinder but not a plan. Shuffled first, the plain tree was 21 to 30 tall over 20 shuffles, and one run averaged 13.97 comparisons a lookup; the balanced tree built from the same shuffle was 13 tall and averaged 10.19. Collecting every badge in random order from the balanced tree of the sorted export took 517 rotations.

A sorted array finds any of the 2,000 in at most 11 probes, the same as the balanced tree, and stores nothing but the names. Its weakness is change: a walk-in placed at random shifts 1,000 names on average, and so does each pickup. The tree pays a few pointer changes instead. These are counts from the lesson’s TypeScript example, not timings.

07 / Give it a real job

File names carefully.

BadgeDesk files each badge under “family, given” with capital letters A to Z folded to lowercase, so staff can type ito or ITO. Everything else compares by code point, which is not any language’s alphabet: Ødegård sorts after Zhou, and a Swedish desk would expect Å, Ä, and Ö at the end while a German one files Ö with O. A real desk sorts with a collator for its locale, and keeps it the same for every comparison, or the tree’s order breaks.

Two attendees can share a name, and this desk refuses the second registration rather than guess. A production desk adds a tie-breaker to the key, such as the registration number, and shows both badges when staff search. Like this desk, it remembers collected badges and refuses a second pickup.

08 / Make the call

Ask whether order and change both matter.

Reach for a balanced search tree when keys keep arriving and leaving and you still need them in order: the next few, the smallest, everything in sequence. Every call stays O(log n) whatever order the keys come in.

Look elsewhere when one of those needs goes away. For lookups by exact name only, a hash map answers in one step. For a list that rarely changes, a sorted array and binary search are smaller. If you only ever need the smallest, a binary heap is simpler. If the question is every name starting with some letters, a trie walks the prefix. And ranges, such as every badge from Castillo to Fischer, are what an ordered map answers.

09 / Take the idea with you

Explain it without saying “binary search tree.”

“Each name has smaller names hanging to its left and larger ones to its right. To find a name I compare from the top and go left or right, one comparison per level. When one side of a name gets two levels taller than the other, I lift the taller child up and move the name down, so the order stays and the levels shrink. To take out a name with names on both sides, I move in the smallest name from its right.” That describes the mechanism. The name is what you call it in a review.

Before moving on, explain three things without the name: why a sorted export made nine levels out of nine names, what one rotation changes, and why Haruki Endo took Camille Dubois’s place. Then look at a list your code keeps sorted by inserting into an array, and decide whether it changes often enough to need a tree.

Connections to follow nextRelated lessons
  • Binary search halves a sorted array the way a balanced tree halves its names, without the pointers.
  • Binary heap keeps only the smallest within reach, in an array, with no rotations.
  • Hash map finds a name in one step when order does not matter.
  • Trie branches on each character instead of each comparison, for questions about prefixes.
  • Ordered map answers floor, ceiling, and range questions over sorted keys, with a skip list instead of rotations.

Take the tree into your editor. Import 100,000 names in order with balancing off and on, count the comparisons to find the last name, then shuffle the names and count again.

Back to data structures & algorithms →