Step 1 -- The everyday picture. A plain binary search tree files each new key by going left when it is smaller and right when it is bigger. That is wonderfully fast -- until the keys happen to arrive already in order. Then every key turns the same way and the tree grows into one long, lopsided chain, as slow to search as an ordinary list. An AVL tree is the same idea with a habit of self-tidying: after every insert it quietly straightens itself so that neither side of any node ever grows much taller than the other.
Step 2 -- Name the quantities, then one rule. Give every node a height (how many levels hang below it) and a balance factor $BF = h_{\text{left}} - h_{\text{right}}$. The single rule that defines an AVL tree is to keep $|BF| \le 1$ at every node. Insert the keys $1, 2, 3$ in that order. After $1$ and $2$ the root ($1$) leans right with $BF = -1$ -- still legal. Add $3$ and the root's balance factor falls to $BF = -2$: too lopsided. The tree responds with a rotation, a small local rearrangement that lifts $2$ up to become the new root with $1$ and $3$ as its children. The height drops from $2$ back to $1$ and searching is quick again.
Step 3 -- The precise version and the payoff. Heights are stored in each node (with the convention $h(\text{null}) = -1$) and updated only along the path back to the root, so restoring balance costs $O(\log n)$. There are exactly four imbalance shapes: LL and RR are fixed by a single rotation, while LR and RL need two. The reward is a proven height bound $h \le 1.44\,\log_2(n+2)$, which holds the cost of search, insert, and delete at $O(\log n)$ regardless of the order keys arrive -- compared with $O(n)$ for an unbalanced BST. In the controls above, the $n$ slider sets how many keys are used and the preset chooses their order: "Sorted" and "Reverse" are precisely the inputs that wreck a naive tree.
Step 4 -- Try this in the sim above. Set the preset to "Sorted" and drag $n$ to its maximum: that ordering would build a degenerate height-$(n-1)$ chain in a naive BST -- the exact disaster AVL is built to prevent (Section 3 shows why the AVL height instead stays near $1.44\,\log_2 n$). Switch between "Random" and "Sorted" and watch the Comparisons counter change. Then walk through with the Next and Prev buttons to follow the AVL-INSERT pseudo-code line by line, including the branch into each of the four rotation cases.
Section 3 -- Complexity Analysis
Symbol
Meaning
n
Input size
T(n)
Time complexity function
h
Height or depth of structure
k
Key or rank parameter
W(n)
Worst-case complexity
A(n)
Average-case complexity
Step 1 -- AVL Invariant. For every node v: |height(v.left) - height(v.right)| <= 1. Called the balance factor (BF). After every insert or delete, BF must be restored via rotations.
Step 2 -- Height Bound. An AVL tree with n nodes has height h <= 1.44 log2(n+2) - 0.328. Proof: minimum nodes N(h) for height h satisfies N(h) = N(h-1) + N(h-2) + 1 (Fibonacci-like). N(h) grows as phi^h/sqrt(5). Inverting: h = O(log n).
Step 3 -- Four Rotation Cases. LL (left-left): single right rotation. RR (right-right): single left rotation. LR (left-right): left rotate child, then right rotate root. RL (right-left): right rotate child, then left rotate root.
Step 4 -- Insert Cost. Standard BST insert O(log n) + update heights O(log n) + at most ONE double or single rotation O(1). Total: O(log n) per insert.
Step 5 -- Delete Cost. BST delete O(log n) + at most O(log n) rotations propagating up the path. Total: O(log n). Deletions may require more rotations than insertions.
Step 6 -- vs Red-Black Tree. AVL: stricter balance (|BF|<=1), faster lookups (lower height ~1.44 log n vs RB ~2 log n), more rotations on insert/delete. Red-Black: fewer rotations (at most 3 total per insert), better for write-heavy workloads. Both O(log n).
Section 4 -- FAQ
What is the balance factor? concept+
BF = height(left subtree) - height(right subtree). AVL invariant: BF in {-1, 0, 1} for every node. BF=2 or -2 after an operation triggers a rotation.
Why exactly 4 rotation cases? concept+
After insert, imbalance BF=2 or -2 at ancestor. Case depends on: (1) which child is heavier (left/right), and (2) which grandchild caused it. Four combinations: LL, RR, LR, RL. LR and RL need two rotations (double rotation).
Where are AVL trees used? applied+
Database transaction logs requiring fast search and infrequent updates. In-memory sorted maps where lookup speed is critical. Linux kernel uses AVL variant for some scheduling structures.
What is the exact height bound 1.44 log n? complex+
From Fibonacci: minimum nodes N(h) at height h satisfies N(0)=1, N(1)=2, N(h)=N(h-1)+N(h-2)+1. Solution N(h) ~ phi^(h+3)/sqrt(5) where phi=(1+sqrt(5))/2. Solving for h: h <= log_phi(sqrt(5)*n) = 1.44 log2(n).
Why might AVL be worse than Red-Black for databases? nonobv+
AVL insert/delete can trigger O(log n) rotations propagating up. For a B-tree-like disk structure, each rotation = disk I/O. Red-Black trees guarantee at most 3 rotations total per insert (2 for delete), minimizing expensive I/O.
How to implement height efficiently? deep+
Store height as an integer field in each node. Update only along the path from insertion point to root. Height(null) = -1 by convention. BF = left.height - right.height. This adds O(1) per node space but enables O(1) BF check.
Section 5 -- Common Misconceptions
Conceptual Misconceptions
X AVL tree guarantees perfectly balanced height.
O AVL guarantees |BF|<=1, not perfect balance. Height is at most 1.44 log2 n, not exactly log2 n.
X Only one rotation needed after insert.
O LL and RR: one rotation. LR and RL (double rotation): two rotations. Delete can require O(log n) rotations.
X AVL trees are always faster than Red-Black trees.
O AVL has lower height (faster search), but more rotations per update. Red-Black trees are generally faster for write-heavy workloads. Choice depends on read/write ratio.
Implementation Errors
X Forgetting to update height after rotation.
O Height update must happen bottom-up: update children before parents. Forgetting causes incorrect BF calculations and silent tree corruption.
X Using height(node) instead of height(node.child) for BF.
O BF = height(root.left) - height(root.right) where height(null) = -1. Using node depth instead of subtree height is a frequent mistake.
X Not handling null children in rotation.
O During LEFT-ROTATE(x): y=x.right must not be null. Right rotation: x.left must not be null. Always assert these conditions before rotating.