Data Structures & Algorithms · Data Structures & Algorithms
AVL Trees
Height-balanced binary search trees: balance factor, rotations, insert, delete and when an AVL beats a plain BST.
Six concepts on AVL trees — the balance-factor invariant, why the worst height is still logarithmic, the four rotation cases, insert, delete, and when an AVL is the wrong tree for disk. Running example: keys 10, 20, 30 and the 20-10-30-5 delete.
- Data Structures & Algorithms
- Hard level
- 6 concepts
1AVL: a BST with |BF| at most 1
An AVL tree is a binary search tree that adds one extra rule: at every node the balance factor has absolute value at most 1. The balance factor of a node v is BF(v) = h(v.\mathrm{left}) - h(v.\mathrm{right}), and the height of an empty child is -1. A leaf therefore has BF = 0. The tree is still a BST — left keys smaller, right keys larger — so search still follows one child per level.
Without that height rule a BST can become a single chain, and search degrades to O(n). The AVL rule is what turns the height label into a guaranteed O(\log n).
Figure. A 3-node AVL. Each node is labelled key and balance factor; all three factors are 0.
How to read one node
- Heights firstHeight of a node is 1 + \max of its children's heights; an empty child is -1.
- SubtractBF = h(\mathrm{left}) - h(\mathrm{right}). Convention is left minus right — flip the sign and every later case name flips with it.
- Legal values-1, 0 or 1 is allowed. |BF| = 2 is the moment a rotation is required.
Balance factors on a 3-node tree
Tree: root 20 with left child 10 and right child 30, both leaves. Compute BF at each node.
- h(10) = h(30) = 0 (leaves)empty children at -1; 1 + \max(-1,-1) = 0
- h(20) = 1 + \max(0, 0)1
- BF(10) = BF(30) = -1 - (-1); BF(20) = 0all three BF = 0
Pro tip. A perfectly full pair of children always gives BF = 0 at the parent. The interesting cases are when one side is one level taller.
A node with left height 1 and a missing right child has balance factor
- 1 - (-1) = 2 — already illegal for an AVL node
- 1 - 0 = 1 — treating a missing child as height 0
- 0, because a missing child does not count
An empty child has height -1, so BF = 1 - (-1) = 2. That node is unbalanced. Height 0 is a leaf, not a missing child.
2Worst AVL height is still O(log n)
The sparsest AVL of height h has one side of height h-1 and the other of height h-2 — the most unbalanced shape the |BF| \le 1 rule still allows. So the minimum number of nodes satisfies n(h) = 1 + n(h-1) + n(h-2) with n(0) = 1 and n(1) = 2. That is a Fibonacci recurrence: n(h) = F_{h+3} - 1.
Fibonacci numbers grow as \varphi^h / \sqrt{5}, so inverting gives h = O(\log n). An AVL cannot become a list. A plain BST can.
Figure. Minimum AVL node counts: 1, 2, 4, 7. Each bar is 1 plus the two previous — Fibonacci, not 2^h.
Why the recurrence is Fibonacci
- Most skewed legalTo minimise nodes at height h, give one child height h-1 and the other h-2.
- Add the rootn(h) = 1 + n(h-1) + n(h-2).
- Invertn(h) \ge c \cdot \varphi^h implies h \le O(\log n).
Minimum nodes for small heights
Compute n(h) for h = 0, 1, 2, 3 from n(h) = 1 + n(h-1) + n(h-2).
- n(0) = 1a single leaf
- n(1) = 2root plus one child
- n(2) = 1 + n(1) + n(0)1 + 2 + 1 = 4
- n(3) = 1 + n(2) + n(1)1 + 4 + 2 = 7
Pro tip. Seven nodes is the smallest AVL of height 3. A complete tree of height 3 already has 15 nodes — the Fibonacci spine is the worst legal AVL, not the typical one.
The minimum number of nodes in an AVL of height 3 is
- 7 — from 1 + n(2) + n(1) = 1+4+2
- 8 — one more than a complete height-2 tree
- 15 — a perfect tree of height 3
The Fibonacci recurrence gives 7. Fifteen is the perfect (most packed) tree of height 3, the opposite extreme.
3Four cases: LL, RR, LR, RL
When a node first reaches |BF| = 2, the repair is a rotation named by the first two child steps from that node toward the inserted (or repaired) key. Left-left and right-right are single rotations. Left-right and right-left are a rotation on the child, then a rotation on the node.
A right rotation on y promotes y's left child x and hangs x's old right subtree on y's left. A left rotation is the mirror. Each rotation is O(1) pointer writes. The BST order is preserved because every key that was between x and y stays between them.
Right rotation on y: y's left child x becomes the new subtree root; x's old right subtree T2 becomes y's new left child; y becomes x's right child. BST order is unchanged because every key in T2 still sits between x and y.
Pick the rotation
- Find the culpritThe lowest node with |BF| = 2 is where you rotate.
- Read two stepsFrom that node, left then left is LL (right-rotate). Right then right is RR (left-rotate).
- The mixed casesLR: left-rotate the left child, then right-rotate the node. RL is the mirror.
| Case | First two steps | Repair |
|---|---|---|
| LL | left, left | right-rotate at the node |
| RR | right, right | left-rotate at the node |
| LR | left, right | left-rotate the left child, then right-rotate the node |
| RL | right, left | right-rotate the right child, then left-rotate the node |
Right rotation
def rotate_right(y):
x = y.left
t2 = x.right
x.right = y
y.left = t2
y.height = 1 + max(h(y.left), h(y.right))
x.height = 1 + max(h(x.left), h(x.right))
return xNode u has BF = +2 and u's left child has BF = -1. The case is
- LR — left then right — left-rotate the left child, then right-rotate u
- LL — a single right rotation at u
- RR — BF = +2 already means the heavy side is the right child
BF(u) = +2 means the left subtree is taller. The left child's BF = -1 means that child's right side is the heavy one, so the two steps are left then right. LL would need the left child also left-heavy. RR is the BF = -2 family.
4Insert: BST hang, then rebalance up
AVL insert is ordinary BST insert — hang the new key at the null child where search dies — then walk back toward the root updating heights. The first ancestor whose |BF| becomes 2 is rotated with the matching case. After that one repair (a single or a double rotation) the path above is balanced again, so insert performs at most one rebalance.
The new key itself is never rotated as the first step; the rotation sits at the lowest unbalanced ancestor.

Insert 30 after 10, 20
- Hang as a BST30 is larger than 10 and 20, so it becomes the right child of 20.
- Update heightsh(30)=0, h(20)=1, h(10)=2, so BF(10) = -1 - 1 = -2.
- RR, then left-rotateTwo steps from 10 toward 30 are right, right. Left-rotate at 10. New root 20, children 10 and 30.
AVL insert
def insert(node, key):
if node is None:
return Node(key)
if key < node.key:
node.left = insert(node.left, key)
elif key > node.key:
node.right = insert(node.right, key)
node.height = 1 + max(h(node.left), h(node.right))
bf = h(node.left) - h(node.right)
if bf > 1 and key < node.left.key:
return rotate_right(node)
if bf < -1 and key > node.right.key:
return rotate_left(node)
if bf > 1 and key > node.left.key:
node.left = rotate_left(node.left)
return rotate_right(node)
if bf < -1 and key < node.right.key:
node.right = rotate_right(node.right)
return rotate_left(node)
return nodeInsert 10, 20, 30
Start from an empty AVL and insert 10, then 20, then 30. Report BF(10) just before the rotation and the root after it.
- insert 10root 10, BF = 0
- insert 20 as right child of 10BF(10) = -1
- insert 30; h(20)=1, h(10)=2BF(10) = -2 (RR)
- left-rotate at 10root 20; children 10, 30; all BF = 0
Pro tip. Name the case from the unbalanced ancestor downward. From 10 the walk to 30 is right then right — that is why the repair is a left rotation, not a right one.
Coding lab. Insert 10, 20, 30 and rotate runs in the app, with checks on your output.
After inserting 10, 20, 30 into an empty AVL, the root is
- 20 — one left rotation at 10 repaired the RR chain
- 10 — a BST insert never changes the first key
- 30 — the newest key is promoted
The chain 10-20-30 is an RR imbalance at 10. A left rotation promotes 20. Leaving 10 as root would keep BF = -2. Promoting 30 would break BST order.
5Delete: BST remove, then rebalance every ancestor
AVL delete starts as BST delete: a leaf is dropped; a one-child node is replaced by that child; a two-child node is replaced by its in-order successor (or predecessor) and that successor is deleted. Then every ancestor of the hole updates its height. Unlike insert, several ancestors can now have |BF| = 2, so you may rotate more than once on the way to the root.
The case names are the same four. After a delete the two-step path is read toward the taller remaining child, not toward a newly inserted key.

Delete 30 from a 4-node AVL
- BST delete30 is a leaf. Remove it.
- RecomputeRoot 20 now has left height 1 and no right child, so BF(20) = 1 - (-1) = 2.
- LL, right-rotateThe remaining child 10 is left-heavy (BF = +1). Right-rotate at 20. New root 10, children 5 and 20.
Delete 30
AVL: root 20, left 10, right 30; 10 has left child 5. Delete 30. What is BF(20) after the drop, and what is the new root?
- drop leaf 3020 has only left subtree 10-5
- h(10)=1, h(\mathrm{right\ of\ }20)=-1BF(20) = 2
- BF(10) = +1 (left-heavy) → LLright-rotate at 20
- new root10, with left 5 and right 20
Pro tip. Insert stops after one rotation. Delete keeps walking: a rotation at a child can unbalance the parent, so you re-check every ancestor.
Compared with AVL insert, AVL delete
- May rotate at several ancestors, not just one
- Never needs a rotation, because removing a node can only shrink height
- Uses a different set of four case names
Height drops can unbalance every ancestor. Shrinking a side is exactly what creates |BF|=2. The case names stay LL, RR, LR, RL.
6When to reach for an AVL
An AVL is the right structure when you need an in-memory ordered map or set — search, predecessor, successor, range — and every write must leave the height O(\log n). Language standard libraries often pick a red-black tree instead: the same O(\log n) guarantee with a looser balance rule and fewer rotations on write-heavy traces.
An AVL is the wrong first pick when the keys live on disk or in a huge file. There a B-tree (or B+ tree) wins, because one node holds many keys and one I/O jumps many levels. Do not treat AVL, red-black and B-tree as synonyms for balanced.
Figure. Bars are a qualitative cost sketch, not a shared unit: skew walks n pointers; AVL walks O(\log n) pointers; a B-tree walks a handful of pages.
Pick the balanced tree
- In-memory ordered mapAVL or red-black. AVL if you want the tighter height; red-black if writes dominate.
- On disk / huge filesB-tree or B+ tree — high fan-out, few I/Os. An AVL of n pointers is the wrong page layout.
- Only inserts at the endA sorted array or a hash table may be enough; balance is a write-anywhere cost.
| Structure | Where it lives | Height / I/O story |
|---|---|---|
| Plain BST | Memory | O(n) if skewed |
| AVL | Memory | O(\log n) height; more rotations |
| Red-black | Memory | O(\log n) height; fewer rotations |
| B-tree / B+ | Disk, filesystems, DBs | Height in pages, not in binary levels |
You need predecessor queries on keys that live in a multi-gigabyte file on disk. First pick
- A B-tree (or B+ tree) — one node, many keys, few I/Os
- An AVL of record pointers — binary height is what disk cares about
- An unbalanced BST — disk makes skew free
Disk cost is pages read, not pointer hops in a binary tree. A B-tree packs many keys per page. An AVL of pointers still walks O(\log n) pages in the worst layout; an unbalanced BST can walk n pages.
Notes
- AVL tree: a BST in which every node's balance factor has absolute value at most 1, so height stays O(\log n).
- Balance factor: height(left) minus height(right), with the empty tree at height -1. Unbalanced means |BF| = 2.
- Four imbalance cases: LL and RR are single rotations; LR and RL are double rotations.
- Insert is ordinary BST insert followed by at most O(\log n) height updates and one (possibly double) rotation on the way back up.
- Delete is BST delete followed by rebalance walking to the root; more than one rotation may be required.
- Use an AVL (or red-black) when you need in-memory ordered search that must stay O(\log n) after arbitrary inserts; use a B-tree when the data lives on disk.
Formulas
- Balance factor: BF(v) = h(v.\mathrm{left}) - h(v.\mathrm{right}), h(\mathrm{null}) = -1.
- AVL invariant: |BF(v)| \le 1 at every node.
- Minimum nodes of height h: n(h) = 1 + n(h-1) + n(h-2) with n(0)=1, n(1)=2, so n(h) = F_{h+3} - 1 and h = O(\log n).
- Search, insert and delete: O(\log n) worst case while the tree stays AVL.
- A single rotation is O(1) pointer writes plus two height recomputes.
Exam traps & shortcuts
- A BST without a balance rule can become a list; O(\log n) is a height claim, not a free gift of the word tree.
- Look at the first two steps from the unbalanced node toward the new key: left-left, left-right, right-right or right-left names the rotation.
- Insert needs at most one rebalance; delete may need one at every ancestor.
- AVL is stricter than red-black (height closer to \log_2 n) and pays more rotations on write-heavy workloads.
Reference tables
Restated from the concepts — scan sheet, not a second argument.
| Quantity | Form |
|---|---|
| Balance factor | BF(v) = h(\mathrm{left}) - h(\mathrm{right}), h(\mathrm{null})=-1 |
| Legal BF | -1, 0, 1 |
| Min nodes of height h | n(h) = 1 + n(h-1) + n(h-2); n(h) = F_{h+3}-1 |
| Search / insert / delete | O(\log n) while AVL |
| Insert rebalances | at most one (single or double) rotation |
| Delete rebalances | possibly one rotation per ancestor |
Same four cases as the rotation concept.
| Case | Repair |
|---|---|
| LL | right-rotate at the unbalanced node |
| RR | left-rotate at the unbalanced node |
| LR | left-rotate the left child, then right-rotate the node |
| RL | right-rotate the right child, then left-rotate the node |
Recap
Night-before AVL pegs.
- Rule
- BST plus |BF| \le 1 at every node. BF = h(\mathrm{left})-h(\mathrm{right}).
- Height
- Sparsest AVL is Fibonacci: n(h)=F_{h+3}-1, so h=O(\log n).
- Cases
- LL / RR single rotate; LR / RL double. Name the two steps from the unbalanced node.
- Insert
- BST hang, walk up, at most one rebalance. 10-20-30 → left-rotate → root 20.
- Delete
- BST delete, then rebalance every ancestor — more than one rotation is normal.
- Use
- In-memory ordered map. Disk / files → B-tree, not a binary AVL of pointers.
Practise AVL 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