An AVL tree is a self-balancing binary search tree where the height difference between the left and right subtrees of any node (the balance factor) is at most 1.
Named after its inventors Adelson-Velsky and Landis (1962) — the first self-balancing BST ever invented.
Balance Factor (BF) = Height(Left Subtree) - Height(Right Subtree)
| BF Value | Meaning |
|---|---|
-1, 0, +1 |
✅ Balanced — valid AVL node |
≤ -2 |
❌ Right-heavy — rotation needed |
≥ +2 |
❌ Left-heavy — rotation needed |
When a node becomes unbalanced after insertion or deletion, one of four rotations restores balance:
Imbalance in the left child's left subtree.
z y
/ \ / \
y T4 → x z
/ \ / \ / \
x T3 T1 T2 T3 T4
Imbalance in the right child's right subtree.
z y
/ \ / \
T1 y → z x
/ \ / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4
Imbalance in the left child's right subtree.
z z x
/ \ / \ / \
y T4 → x T4 → y z
\ / / \ / \
x y T1 T2T3 T4
Imbalance in the right child's left subtree.
z z x
/ \ / \ / \
T1 y → T1 x → z y
/ \ /\ / \
x y T1 T2T3 T4
| Operation | Average | Worst Case |
|---|---|---|
| Search | O(log n) | O(log n) |
| Insert | O(log n) | O(log n) |
| Delete | O(log n) | O(log n) |
| Space | O(n) | O(n) |
The worst case is guaranteed O(log n) — unlike a regular BST which degrades to O(n) on sorted input.
class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1 # New node starts at height 1
class AVLTree:
# ── Helpers ──────────────────────────────────────────
def get_height(self, node):
return node.height if node else 0
def get_balance(self, node):
if not node:
return 0
return self.get_height(node.left) - self.get_height(node.right)
def update_height(self, node):
node.height = 1 + max(self.get_height(node.left),
self.get_height(node.right))
# ── Rotations ────────────────────────────────────────
def rotate_right(self, z):
y = z.left
T3 = y.right
y.right = z # perform rotation
z.left = T3
self.update_height(z)
self.update_height(y)
return y # new subtree root
def rotate_left(self, z):
y = z.right
T2 = y.left
y.left = z
z.right = T2
self.update_height(z)
self.update_height(y)
return y
# ── Insert ───────────────────────────────────────────
def insert(self, node, key):
# 1. Standard BST insert
if not node:
return Node(key)
if key < node.key:
node.left = self.insert(node.left, key)
elif key > node.key:
node.right = self.insert(node.right, key)
else:
return node # duplicates not allowed
# 2. Update height
self.update_height(node)
# 3. Get balance factor and rebalance
balance = self.get_balance(node)
# LL
if balance > 1 and key < node.left.key:
return self.rotate_right(node)
# RR
if balance < -1 and key > node.right.key:
return self.rotate_left(node)
# LR
if balance > 1 and key > node.left.key:
node.left = self.rotate_left(node.left)
return self.rotate_right(node)
# RL
if balance < -1 and key < node.right.key:
node.right = self.rotate_right(node.right)
return self.rotate_left(node)
return node
# ── Search ───────────────────────────────────────────
def search(self, node, key):
if not node or node.key == key:
return node
if key < node.key:
return self.search(node.left, key)
return self.search(node.right, key)
# ── In-order Traversal ───────────────────────────────
def inorder(self, node, result=None):
if result is None:
result = []
if node:
self.inorder(node.left, result)
result.append(node.key)
self.inorder(node.right, result)
return result
# ── Usage ─────────────────────────────────────────────────
tree = AVLTree()
root = None
for key in [30, 20, 10, 25, 40]:
root = tree.insert(root, key)
print(tree.inorder(root)) # [10, 20, 25, 30, 40]
print(tree.search(root, 25)) # <Node object>
| Feature | AVL Tree | Red-Black Tree | B-Tree |
|---|---|---|---|
| Balance guarantee | Strict (BF ≤ 1) | Relaxed | Relaxed |
| Search speed | ✅ Faster | Slightly slower | Good for disk |
| Insert/Delete speed | Slightly slower | ✅ Faster | Good for disk |
| Rotations per insert | ≤ 2 | ≤ 2 | — |
| Best for | Read-heavy workloads | Write-heavy workloads | Databases / filesystems |