← All Posts
DSA Series · Trees · AVL Trees · Part 2

Rotations

In the previous post we saw that AVL trees maintain a balance factor of {-1, 0, +1} at every node. When an insertion or deletion pushes some node's balance factor to +2 or -2, we need to fix it. The fix is always a rotation: a local restructuring of 2 or 3 nodes that restores balance without breaking the BST ordering.

There are exactly four cases, and once you understand the first two, the other two are just combinations.

What a Rotation Actually Does

A rotation changes the parent-child relationship between two nodes while preserving the in-order sequence. Think of it as "pulling" a child up to become the parent, and the old parent drops down to become a child.

The critical property: a rotation never changes the in-order traversal. Before and after rotation, the same keys appear in the same sorted order. It only changes the tree's shape (depth of nodes).

Rotation rule: in-order stays the same, shape changes Before: A < B < C B is child of A After: A < B < C B is parent of A Same sorted order. Different tree height. That's a rotation.

Right Rotation (LL Case)

When a node Z becomes unbalanced because its left child's left subtree is too tall, we do a right rotation at Z. The left child Y moves up to take Z's place, and Z becomes Y's right child.

The Setup

Node Z has balance factor +2. Its left child Y has balance factor +1 or 0 (left-heavy or balanced). The imbalance came from Y's left subtree (the "left-left" path).

Before (unbalanced) Z bf=+2 Y bf=+1 T3 X T2 T1 right rotate at Z After (balanced) Y bf=0 X Z T1 T2 T3 In-order: T1, X, T1' ... Y ... T2, Z, T3 (unchanged!) Key move: T2 transfers from Y's right to Z's left
Right rotation at Z: Y rises, Z drops right, T2 moves from Y's right child to Z's left child.

What Happens Step by Step

  1. Y (Z's left child) becomes the new root of this subtree.
  2. Z becomes Y's right child.
  3. T2 (Y's old right subtree) becomes Z's new left child. This is the tricky part. T2 has keys between Y and Z, so it's valid as Z's left child.
  4. Update heights of Z first (it's now lower), then Y.

Code

TreeNode* rotateRight(TreeNode* z) {
    TreeNode* y = z->left;
    TreeNode* T2 = y->right;

    y->right = z;        // Z drops to right
    z->left = T2;        // T2 transfers

    z->height = 1 + max(height(z->left), height(z->right));
    y->height = 1 + max(height(y->left), height(y->right));

    return y;  // Y is the new root
}
Why does T2 move? Before the rotation, T2 was Y's right child (all keys in T2 are greater than Y). After rotation, Z needs a left child (all keys must be less than Z). Since Y < T2 < Z, T2 fits perfectly as Z's left child.

Left Rotation (RR Case)

The mirror of right rotation. When Z is unbalanced because its right child's right subtree is too tall, we do a left rotation at Z.

Before (bf = -2) Z bf=-2 T1 Y T2 X After (balanced) Y Z X T1 T2 T2 moves from Y's left to Z's right
Left rotation at Z: mirror of right rotation. Y rises, Z drops left, T2 transfers.
TreeNode* rotateLeft(TreeNode* z) {
    TreeNode* y = z->right;
    TreeNode* T2 = y->left;

    y->left = z;         // Z drops to left
    z->right = T2;       // T2 transfers

    z->height = 1 + max(height(z->left), height(z->right));
    y->height = 1 + max(height(y->left), height(y->right));

    return y;
}

Left-Right Double Rotation (LR Case)

This is where it gets interesting. Z has bf = +2 (left-heavy), but its left child Y is right-heavy (bf = -1). A single right rotation won't fix this because the heavy subtree is on the "wrong side" of Y.

The solution: two rotations. First left-rotate at Y to straighten the zig-zag into a straight line, then right-rotate at Z.

Original (LR zig-zag) Z +2 Y -1 X left rotate Y After left-rotate at Y (now LL) Z X Y right rotate Z Balanced ✓ X Y Z Why two rotations? The zig-zag pattern (Z left to Y, then right to X) can't be fixed by a single rotation. Step 1 (left-rotate Y) straightens the zig-zag into a straight LL line. Step 2 (right-rotate Z) is then the normal LL fix we already know. The middle value (X) always ends up as the new root. Makes sense: it's the median!
LR case: first straighten the zig-zag with a left rotation, then fix with a right rotation.
// LR case: left-rotate child, then right-rotate node
if (bf > 1 && balance(node->left) < 0) {
    node->left = rotateLeft(node->left);   // straighten zig-zag
    return rotateRight(node);               // normal LL fix
}

Right-Left Double Rotation (RL Case)

Mirror of LR. Node Z has bf = -2, and its right child Y has bf = +1 (left-heavy). The zig-zag goes right then left.

Fix: right-rotate at Y first (straighten), then left-rotate at Z.

// RL case: right-rotate child, then left-rotate node
if (bf < -1 && balance(node->right) > 0) {
    node->right = rotateRight(node->right); // straighten zig-zag
    return rotateLeft(node);                 // normal RR fix
}

The Four Cases Cheat Sheet

CaseZ's bfChild's bfPatternFix
LL+2≥ 0Left-Left (straight)Right rotate Z
RR-2≤ 0Right-Right (straight)Left rotate Z
LR+2< 0Left-Right (zig-zag)Left rotate child, right rotate Z
RL-2> 0Right-Left (zig-zag)Right rotate child, left rotate Z
Memory aid: Straight paths (LL, RR) need one rotation in the opposite direction. Zig-zag paths (LR, RL) need two rotations: first straighten, then fix. The median of the three nodes always becomes the new root.

Interactive: Watch All Four Rotations

Click a rotation type to see it animated.

Rotation Cost: O(1)

Each rotation (single or double) involves a constant number of pointer reassignments and height updates. No matter how large the tree, a rotation takes O(1) time. The expensive part of insert/delete is finding where the imbalance is (O(log n) walk), not fixing it.

What's Next

Now that we understand all four rotation cases, the next post walks through the full AVL insert and delete algorithms: how we find the imbalance, decide which rotation to apply, and update heights on the way back up the recursion.