Red-Black Trees
Self-balancing binary search trees that recolor and rotate to keep height logarithmic.
What is it?
Red-black trees add a color bit to each node and enforce invariants (root is black, no two reds in a row, every path has the same number of black nodes). When inserts or deletes break an invariant, a handful of rotations and recolors repair it. The result is an ordered map/set that stays balanced even with adversarial input, which is why languages like C++, Java, Swift, and Rust use them for their core associative containers.
Time Complexity
Implementation Example
class RBNode {
constructor(value, color = 'red') {
this.value = value;
this.color = color;
this.left = null;
this.right = null;
this.parent = null;
}
}
class RedBlackTree {
constructor() {
this.root = null;
}
rotateLeft(node) {
const pivot = node.right;
node.right = pivot.left;
if (pivot.left) pivot.left.parent = node;
pivot.parent = node.parent;
if (!node.parent) {
this.root = pivot;
} else if (node === node.parent.left) {
node.parent.left = pivot;
} else {
node.parent.right = pivot;
}
pivot.left = node;
node.parent = pivot;
}
rotateRight(node) {
const pivot = node.left;
node.left = pivot.right;
if (pivot.right) pivot.right.parent = node;
pivot.parent = node.parent;
if (!node.parent) {
this.root = pivot;
} else if (node === node.parent.right) {
node.parent.right = pivot;
} else {
node.parent.left = pivot;
}
pivot.right = node;
node.parent = pivot;
}
insert(value) {
const newNode = new RBNode(value);
if (!this.root) {
newNode.color = 'black';
this.root = newNode;
return;
}
let parent = this.root;
while (true) {
if (value < parent.value) {
if (!parent.left) {
parent.left = newNode;
break;
}
parent = parent.left;
} else {
if (!parent.right) {
parent.right = newNode;
break;
}
parent = parent.right;
}
}
newNode.parent = parent;
this.fixInsert(newNode);
}
fixInsert(node) {
while (node.parent && node.parent.color === 'red') {
const grand = node.parent.parent;
if (node.parent === grand.left) {
const uncle = grand.right;
if (uncle && uncle.color === 'red') {
node.parent.color = 'black';
uncle.color = 'black';
grand.color = 'red';
node = grand;
} else {
if (node === node.parent.right) {
node = node.parent;
this.rotateLeft(node);
}
node.parent.color = 'black';
grand.color = 'red';
this.rotateRight(grand);
}
} else {
const uncle = grand.left;
if (uncle && uncle.color === 'red') {
node.parent.color = 'black';
uncle.color = 'black';
grand.color = 'red';
node = grand;
} else {
if (node === node.parent.left) {
node = node.parent;
this.rotateRight(node);
}
node.parent.color = 'black';
grand.color = 'red';
this.rotateLeft(grand);
}
}
}
this.root.color = 'black';
}
}Red-Black Trees
Red-Black Trees Deep Dive
Red-black trees are binary search trees with a color bit and simple invariants that keep the tree “balanced enough” without expensive rebuilds.
?But why does this matter?
Localized rotations and recolors run only when needed, so inserts and deletes stay O(log n) even if inputs arrive in sorted or adversarial order.
Visual Flow
How it actually moves
Lookup
O(log n)Binary search tree traversal obeys in-order property.
Insertion
O(log n)Insert like a BST, then repair invariants via recolor/rotation.
Deletion
O(log n)Replace node, propagate double-black fixes until tree stabilizes.
Watch the best explainer
Red-Black Trees in 4 Minutes — Intro
Michael Sambol
The most popular quick visual intro to red-black rules and rotations.
City zoning that enforces alternating districts
Picture urban planners who paint districts red or black and demand that every route from city hall to a neighborhood alternates colors and passes the same number of black districts. When construction breaks the rules, they repaint or reroute a couple streets to keep commute times predictable.
Real-world analogy
Storage engines keep fresh writes in an in-memory red-black tree (memtable). Even when one tenant hammers a key range, lookups for everyone else remain fast because the tree height never blows up.
Takeaway
Instead of perfection, red-black trees promise “never too tall,” giving you predictable latency with minimal balancing overhead.
Practical applications
• Ordered maps/sets in standard libraries (C++ std::map, Java TreeMap, Swift OrderedDictionary).
• Kernel schedulers (Linux CFS) and memory managers that sort entities by priority or address.
• Compilers and IDEs that manage symbol tables for million-line codebases.
Technical insights
• The rules “root is black”, “no two reds in a row”, and “equal black height on every path” bound the tree height to ≤ 2*log2(n).
• Recoloring handles most violations; rotations only run when the structure needs to pivot.
• Deletion fix-ups treat “double black” nodes as debt that gets paid as you bubble toward the root.
When to reach for it
• Ordered key/value storage that must stay fast even with adversarial patterns.
• Systems that need range queries in addition to point lookups.
• Schedulers or caches where periodic “rebalance everything” pauses are unacceptable.
Related topics
- trees
- hash-tables
- heaps
Operations Breakdown
Lookup
O(log n)Binary search tree traversal obeys in-order property.
Insertion
O(log n)Insert like a BST, then repair invariants via recolor/rotation.
Deletion
O(log n)Replace node, propagate double-black fixes until tree stabilizes.
Best practices
• Augment nodes with subtree size or aggregates (sum, min) to support order-statistic queries without reworking balancing logic.
• Use pool allocators when the tree churns heavily to reduce fragmentation and GC pressure.
• Expose debugging traversals (color, black-height) so automated tests can assert invariants.
Common pitfalls
• Skipping recolor steps before rotating can silently break invariants until production traffic reveals it.
• Recursive deletion fix-ups risk stack overflows on untrusted depth; keep algorithms iterative.
• Iterators become invalid after structural edits; guard with locks or copy-on-write snapshots.
Every kind of Red-Black Trees
Interviewers rarely say which flavor they mean — they expect you to recognize it from the problem. Each variant below comes with the algorithms it unlocks.
Type 1 of 4
Red-Black Tree
→ full pageThe production-default balanced BST: five color rules guarantee the longest path is at most twice the shortest, so height stays O(log n).
What to know
- Rules: nodes are red or black; root black; no red-red parent-child; equal black-height on every path.
- Insert fixes violations with recolors and at most two rotations.
- Delete is the hard case — "double black" resolution has four sub-cases.
Algorithms to reach for
Insert fix-up
O(log n)Restore color rules after insertion
Delete fix-up
O(log n)Restore black-height after removal
In the wild: Java TreeMap, C++ std::map/set, Linux CFS run-queue, nginx timers.
Practice problems — hints, approach & solution on their own pages
Type 2 of 4
AVL Tree
→ full pageThe strictest balancer: sibling heights differ by at most 1. Reads are fastest of any BST; writes pay for it with more rotations.
What to know
- Balance factor ∈ {−1, 0, 1} stored per node; violations trigger LL/RR/LR/RL rotations.
- Height ≤ 1.44 log n — measurably shorter than red-black’s 2 log n bound.
- Choose AVL for read-heavy workloads, red-black for mixed ones.
Algorithms to reach for
Rotation rebalancing
O(log n)Restore balance factor after each write
Height-tracked search
O(log n)Fastest guaranteed BST lookups
In the wild: Read-heavy in-memory indexes, language runtimes, early filesystem trees.
Practice problems — hints, approach & solution on their own pages
Type 3 of 4
Splay Tree
→ full pageNo stored balance data at all: every access rotates the touched node to the root, so hot items become cheap automatically.
What to know
- Zig, zig-zig, zig-zag rotation patterns move accessed nodes up.
- Amortized O(log n) even though a single operation can be O(n).
- Naturally cache-like: recently used keys sit near the root.
Algorithms to reach for
Splaying
O(log n) amortizedSelf-optimizing access; hot keys float to top
Split / join
O(log n) amortizedCut and merge ordered sets by splaying a boundary
In the wild: Caches with skewed access, rope data structures in editors, network flow libraries.
Type 4 of 4
Treap (Tree + Heap)
→ full pageEach node gets a random priority; BST order by key, heap order by priority. Randomness does the balancing with no case analysis.
What to know
- Equivalent to a BST built by inserting keys in random order — expected height O(log n).
- split(key) and merge(a, b) make ordered-set union/insertion elegant.
- Far simpler to code correctly under pressure than red-black deletion.
Algorithms to reach for
Split / merge
O(log n) expectedCompose all ordered-set ops from two primitives
Implicit treap
O(log n) expectedArray with O(log n) insert/delete/reverse anywhere
In the wild: Competitive programming workhorse, versioned buffers, randomized indexes.
Signature algorithms
Algorithm
Insertion fix-up
After inserting a red node, walk upward repairing red-on-red violations by recoloring and rotating around the grandparent.
Time
O(log n)
Space
O(1)
function fixInsert(tree, node) {
while (node.parent && node.parent.color === 'red') {
const grand = node.parent.parent;
if (node.parent === grand.left) {
const uncle = grand.right;
if (uncle && uncle.color === 'red') {
node.parent.color = 'black';
uncle.color = 'black';
grand.color = 'red';
node = grand;
} else {
if (node === node.parent.right) {
node = node.parent;
tree.rotateLeft(node);
}
node.parent.color = 'black';
grand.color = 'red';
tree.rotateRight(grand);
}
} else {
const uncle = grand.left;
if (uncle && uncle.color === 'red') {
node.parent.color = 'black';
uncle.color = 'black';
grand.color = 'red';
node = grand;
} else {
if (node === node.parent.left) {
node = node.parent;
tree.rotateRight(node);
}
node.parent.color = 'black';
grand.color = 'red';
tree.rotateLeft(grand);
}
}
}
tree.root.color = 'black';
}