Skip to content

Balanced Trees

advanced · 30 min · Last reviewed 31 Oct 2026 · by TheLearningLoop Team

Key takeaways

  • A plain BST becomes a line when keys arrive sorted. A self-balancing tree rotates after inserts and deletes to keep the height O(log n).
  • The AVL rule: at every node the left and right heights differ by at most 1. Four cases (left-left, right-right, left-right, right-left) are fixed with one or two rotations.
  • A rotation changes the shape but never the in-order order of keys. AVL trees search faster, and red-black trees update with fewer rotations.

New to this topic? Read the overview first: Data Structures & Algorithms: The Big Picture

When to use a balanced tree

Use a balanced search tree when you need:

  • Ordered data that changes: insert, delete and search in O(log n) while keeping everything sorted.
  • Guaranteed O(log n) even for bad input such as already-sorted data, where a plain binary search tree degrades to O(n).
  • Ordered queries a hash map cannot answer: the next larger key, the previous smaller key, everything in a range, or the k-th smallest.

Use something else when:

  • You only need exact lookups: use a hash table, which is O(1) on average.
  • The data is built once and then only read: a sorted array with binary search is simpler and faster.
  • The data lives on disk: use a B-tree, which packs many keys per node to cut disk reads.
  • You only need the smallest or largest item: use a heap.

Do not write your own balanced tree in production. Use TreeMap, std::map or the like (see the real-world section). Writing an AVL tree by hand is for learning and interviews.

How a balanced tree works

A plain binary search tree keeps smaller keys on the left and larger keys on the right, which makes search fast only while the tree is short. Insert keys in sorted order and every new key goes to the right, so the tree becomes a straight line and every search costs O(n).

Intuition. Think of a phone book. You open it in the middle and halve the pages again and again, and that only works because the names are spread evenly. Now imagine every new name is stapled on at the end, in alphabetical order. The book is one long chain and you must read from the front. A balanced tree is a phone book that re-spreads itself as names arrive.

Here is the problem in three inserts. Put 1, 2 and 3 into a plain tree and you get a line:

1
 \
  2
   \
    3        height 3, a straight line

A balanced tree turns the same keys into this, with the same left-to-right order 1, 2, 3:

  2
 / \
1   3        height 2

A self-balancing tree repairs itself after each insert or delete so its height stays about log n. The repair is a rotation: re-linking a few nodes to move a subtree up one level without breaking the ordering rule.

AVL rule: at every node, the heights of the left and right subtrees differ by at most 1. The difference is called the balance factor and is always -1, 0 or +1.

Counting convention. The height of an empty subtree is 0 and the height of a leaf is 1. The balance factor of a node is the height of its left subtree minus the height of its right subtree. So +2 means "too heavy on the left" and -2 means "too heavy on the right".

If an insert makes a balance factor +2 or -2, a rotation (or two) restores it. A rotation touches only a few nodes, so it costs O(1). After the repair, the rebalanced subtree has the same height it had before the insert, so no node higher up changes and nothing more needs fixing. That is why an insert causes at most one rebalancing: either a single rotation or a double rotation (two rotations).

There are two common families:

  • AVL trees keep the strictest balance. Height is at most about 1.44 × log2(n), so searches are fastest, and inserts do slightly more rotating.
  • Red-black trees keep a looser rule using node colours (red or black) so that the longest path is at most twice the shortest. They rotate less on inserts and deletes, so updates are cheaper. Java's TreeMap and C++'s std::map typically use them.

Try it

Insert the same numbers into a plain tree and an AVL tree and compare the heights.

Balanced Trees

Step 1 of 17 · Press Space to play, arrow keys to step

The tree is empty.

Initial state. Insert 1, 2, 3, 4, 5, 6, 7 one at a time into an AVL tree. Watch the height.

step:
0
height:
0
rotations:
0
comparisons:
0
nodes:
0

Things to try:

  • Choose Plain binary search tree and 1 2 3 4 5 6 7 (sorted). The height grows to 7: a straight line, so a search may take 7 comparisons.
  • Choose AVL tree with the same input. After 3 inserts it rotates, and at the end the height is 3 with 4 rotations in all. Every search takes at most 3 comparisons.
  • Choose AVL tree and 30 10 20 50 40 60. Inserting 20 makes a zig-zag, which needs a double rotation: first straighten, then rotate. The tones show which node is out of balance and which becomes the new top. Over the whole run the tree rotates 5 times: two double rotations (when 20 and 40 arrive) and one single rotation (when 60 arrives). The final height is 3.
  • Choose AVL tree and 4 2 6 1 3 5 7. No rotation is needed and the height is 3. The insert order matters more than the tree type, but only the AVL tree is safe for every order.

What to watch. Before you press Next on an "out of balance" step, work out the balance factors yourself: height of the left subtree minus height of the right subtree. Then predict which of the four cases it is and whether one or two rotations follow. Compare your answer with the caption.

Balanced tree operations: search, insert, delete and rotate

Search. The same as in a plain BST: go left if the target is smaller, right if larger. Height is O(log n), so this is O(log n).

Insert. Insert like a plain BST, then walk back up to the root, updating heights. At the first node whose balance factor is +2 or -2, rotate. One rotation (or one double rotation) restores the balance for the whole tree.

Delete. Delete like a plain BST, then walk back up, updating heights and rotating wherever the balance is broken. Unlike insert, a delete may need a rotation at several levels, still O(log n) in total.

The four imbalance cases (the node z is out of balance):

  1. Left-left (left child is heavy on its left): rotate right at z.
  2. Right-right (right child is heavy on its right): rotate left at z.
  3. Left-right (left child is heavy on its right): rotate left at the child, then right at z.
  4. Right-left (right child is heavy on its left): rotate right at the child, then left at z.

A rotation keeps the in-order sequence unchanged. This is the interview fact: it only changes shape, never the order of keys, which is why the tree is still a valid search tree afterwards.

Worked example: reading and fixing an imbalance by hand

The recipe is always the same. Walk back up from the new leaf, find the first node z with balance factor +2 or -2, then look at the balance factor of its heavy child. Same sign on both: one rotation. Opposite signs: two.

Example A: insert 10, 20, 30. After 30 is added the tree is a line, and node 10 has balance factor 0 - 2 = -2. Its heavy child, 20, has factor -1: same side, so this is right-right. Rotate left at 10.

10                 20
  \               /  \
   20     -->    10    30
     \
      30

Example B: insert 30, 10, 20. After 20 is added, node 30 has factor 2 - 0 = +2. Its heavy child, 10, has factor -1: opposite sides, so this is left-right. First rotate left at 10, which straightens the zig-zag into a line. Then rotate right at 30.

    30             30              20
   /              /               /  \
 10      -->    20       -->    10    30
   \           /
    20        10

In both examples the in-order sequence is 10, 20, 30 before and after. Only the shape changed.

Example C: a longer run. Insert 10, 20, 30, 40, 50, 25 into an empty AVL tree. The tree rotates when 30 arrives (right-right at 10), when 50 arrives (right-right at 30) and when 25 arrives (right-left at 20, a double rotation). That is 4 rotations in total and the final tree has root 30, with 20 (children 10 and 25) on the left and 40 (right child 50) on the right. Its height is 3, where a plain tree would have a height of 5 for the first five keys alone.

Balanced tree time complexity

OperationPlain BST (average)Plain BST (worst)AVL treeRed-black tree
SearchO(log n)O(n)O(log n)O(log n)
InsertO(log n)O(n)O(log n), at most 2 rotationsO(log n), at most 2 rotations
DeleteO(log n)O(n)O(log n), up to O(log n) rotationsO(log n), at most 3 rotations
HeightAverage search path about 1.39 log2 n for random datanAt most about 1.44 log2 nAt most 2 log2 (n + 1)
SpaceO(n)O(n)O(n) plus a height or factor per nodeO(n) plus a colour bit per node

n is the number of keys. The worst case of a plain BST happens when keys arrive sorted. Balancing removes it at the cost of a small extra field per node and a few rotations.

Why the height is logarithmic. Ask how few nodes an AVL tree of height h can have. The sparsest one has a root, one subtree of height h - 1 and one of height h - 2, so its size is N(h) = N(h-1) + N(h-2) + 1. That grows like the Fibonacci numbers, which is exponential in h. So n nodes force h to be at most about 1.44 × log2(n), never close to n.

Balanced trees vs other data structures

JobAVL / red-black treePlain BSTHash tableSorted arraySkip list
Exact lookupO(log n)O(n) worstO(1) averageO(log n)O(log n)
InsertO(log n) guaranteedO(n) worstO(1) averageO(n)O(log n)
DeleteO(log n) guaranteedO(n) worstO(1) averageO(n)O(log n)
In-order walkO(n)O(n)O(n log n), must sortO(n)O(n)
Next larger / smaller keyO(log n)O(n) worstO(n)O(log n)O(log n)
Range queryO(log n + k)O(n) worstO(n)O(log n + k)O(log n + k)
Guaranteed worst caseYesNoNoYes for readsNo (probabilistic)

n is the number of keys and k the number of results in a range. A balanced tree wins when you need ordered operations and updates together, and it never beats a hash table at exact lookups alone. A skip list is a layered linked list that gets the same ordered behaviour from random coin flips instead of rotations.

Edge cases

  • Empty tree. The first insert becomes the root with height 1 and no rotation.
  • One or two keys. Balance factors stay within -1 to +1, so the first rotation can only happen on the third key.
  • Duplicate keys. The simulation sends an equal key to the right. Library maps such as TreeMap keep one entry per key and replace its value, and a multiset allows copies. Rotations keep the in-order order either way, but decide your duplicate rule before you write the insert.
  • Reverse-sorted input. The mirror image of sorted input: left-left cases and right rotations. Inserting 7, 6, 5, 4, 3, 2, 1 also ends with height 3 after 4 rotations.
  • Delete a node with two children. Replace it with its in-order successor (or predecessor), remove that node, then walk up and rebalance.
  • Delete where the heavy child has factor 0. This cannot happen on insert. A single rotation fixes it, and the height of that subtree does not shrink, so the walk up can stop.
  • Very large inputs. The height stays about log2(n), so even a million keys need only about 20 to 30 levels of recursion.

Common mistakes

  • Forgetting to update heights after a rotation. Update the lower node first, then the new top, or the balance factors above will be stale.
  • Picking the case from z alone. You also need the balance factor of the heavy child, otherwise you cannot tell a single rotation from a double one.
  • Rotating at the wrong node. Rotate at the first (lowest) out-of-balance node on the way back up, not at the root.
  • Stopping the walk up after a delete. An insert can stop after one rebalancing, but a delete may shrink a subtree and unbalance a node higher up.
  • Thinking "balanced" means "perfect". The tree only needs its height to stay O(log n). Its two sides may still differ by one level.
  • Believing a rotation changes the order. It changes only the shape. The in-order sequence is always the same.

How to spot a balanced-tree problem

Look for these signals in a problem statement:

  • Items arrive and leave over time and you must keep them in sorted order.
  • You are asked for the next larger, previous smaller, floor or ceiling of a value, or for everything in a range.
  • The input may arrive already sorted, or an adversary may choose it, so you need a guaranteed O(log n) and not an average.
  • The statement sounds like a calendar or booking check ("does this new interval overlap an existing one?") or a running median over a sliding window.

If none of these appear, a hash table (exact lookups), a heap (only the smallest or largest) or a sorted array (read-only data) is simpler. In an interview, reach for the language's ordered map or set, such as TreeMap or std::set, and be ready to explain how it stays balanced.

How to explain balanced trees in an interview

  • State the problem first: "a plain BST can become a line, so search is O(n). A balanced tree keeps the height O(log n)."
  • State the AVL invariant: "left and right heights differ by at most 1 at every node." Then name the four cases and say that each is fixed with one or two rotations in O(1).
  • Say that rotations preserve the in-order order, and that insert needs at most one rebalancing, which is one or two rotations.
  • Be ready to trace a short insert sequence by hand, naming the case (left-left, left-right and so on) at each rotation.
  • Compare AVL and red-black trees: "AVL is stricter, so lookups are faster; red-black rotates less, so updates are cheaper. Standard libraries mostly use red-black trees or B-trees."

Where you meet balanced trees in real systems

You rarely write an AVL tree at work, but balanced search trees sit underneath the sorted maps, schedulers and indexes you use. Here are three of them drawn out, the first with code you can run, then more examples.

Example 1: An event timeline indexed by timestamp

A scheduling or logging service stores events keyed by timestamp. Events usually arrive in time order, which is exactly the input that turns a plain binary search tree into a straight line. A balanced tree rotates as it goes, so "find the first event after 10:30" or "list everything between 10:00 and 10:15" stays O(log n). Open the example below to see the picture and the code.

Code: AVL insert with rotationsTime O(log n)Memory O(n)
4261357
A plain tree would be a line of height 7. The AVL tree rotated four times along the way and ends perfectly balanced with height 3, with 4 at the top.
# AVL tree insert with rotations. Every node keeps its height, and the heights of its two
# subtrees may differ by at most 1.
# Time: O(log n) per insert (at most 2 rotations). Memory: O(n) for n nodes, O(log n) recursion depth.
class Node:
    def __init__(self, key):
        self.key, self.left, self.right, self.h = key, None, None, 1

def h(n):
    return n.h if n else 0

def fix(n):
    n.h = 1 + max(h(n.left), h(n.right))

rotations = 0

def rotate_right(z):
    global rotations
    rotations += 1
    y = z.left
    z.left, y.right = y.right, z      # y moves up, z becomes its right child
    fix(z); fix(y)
    return y

def rotate_left(z):
    global rotations
    rotations += 1
    y = z.right
    z.right, y.left = y.left, z
    fix(z); fix(y)
    return y

def insert(n, key):
    if n is None:
        return Node(key)
    if key < n.key:
        n.left = insert(n.left, key)
    else:
        n.right = insert(n.right, key)
    fix(n)
    bf = h(n.left) - h(n.right)       # balance factor
    if bf > 1:                        # left side too tall
        if h(n.left.left) < h(n.left.right):
            n.left = rotate_left(n.left)      # left-right case: straighten first
        return rotate_right(n)
    if bf < -1:                       # right side too tall
        if h(n.right.right) < h(n.right.left):
            n.right = rotate_right(n.right)   # right-left case: straighten first
        return rotate_left(n)
    return n

def preorder(n, out):
    if n:
        out.append(n.key); preorder(n.left, out); preorder(n.right, out)

root = None
for k in range(1, 8):                 # sorted input: a plain BST would become a straight line
    root = insert(root, k)
order = []
preorder(root, order)
print("height:", h(root))
print("preorder:", *order)
print("rotations:", rotations)

Each version inserts the keys 1 to 7 in order into an AVL tree, then prints the height, the keys in pre-order and the number of rotations. Every version was compiled and run, and printed height 3, pre-order 4 2 1 3 6 5 7 and 4 rotations. The Rust version uses Option<Box<Node>>, and the C++ version uses std::unique_ptr, so ownership is explicit.

Example 2: Database indexes

A relational database keeps an index on a column as a B-tree, a balanced tree where each node holds many keys and has many children. The same balancing idea applies, but a node fills one disk page, so a lookup in a table of a billion rows reads only a few pages. Range queries find the first key and then read forward in order.

Read root pagepick child by rangeRead middle pagepick childRead leaf pagefind the keyFollow leaf linksfor a range query
Each step reads one page, picks the child whose range contains the key, and goes down. A tall, narrow tree is replaced by a short, wide one: three or four page reads can cover a very large table.

Example 3: Reading balance factors

After every insert or delete, a balanced tree walks back up the path it used and recomputes each node's balance factor (left height minus right height). The first node that reads 2 or -2 is where the rotation happens. Here 10, 20 and 30 were inserted in order, so the chain leans right.

Node102030Balance factor-2-10
After an insert, each node on the path gets a new balance factor (left height minus right height). The first node reading 2 or -2 is where the rotation happens.

More real-world scenarios

Scenarios 4 to 6 describe named libraries and systems. Scenarios 7 to 12 describe typical designs and are illustrative, not claims about any specific product.

4. Ordered maps in standard libraries

Java's TreeMap and TreeSet, and C++'s std::map and std::set, are typically red-black trees. They give ordered iteration, floor and ceiling lookups and range views with guaranteed O(log n) operations.

5. Linux and operating systems

The Linux kernel uses red-black trees in several places, for example to track memory areas of a process and, for a long time, the tasks of its scheduler. The kernel ships a generic red-black tree library for this.

6. Java's HashMap buckets

When many keys collide in one bucket of a Java HashMap, the bucket is turned from a linked list into a red-black tree once it grows past a small threshold. A flood of colliding keys then costs O(log n) per lookup instead of O(n).

7. Interval and calendar queries

A calendar that must answer "is this time free?" keeps booked intervals in a balanced tree sorted by start time, and finds the neighbours of a new booking in O(log n).

8. Order books and price ladders

Exchanges keep buy and sell orders sorted by price so the best price is always at one end. Orders arrive and are cancelled constantly, so insert and delete must be fast and ordered.

9. Leaderboards and rankings

A game that needs "the three players just above me" or "everyone between 1,000 and 2,000 points" needs data that stays sorted while scores change.

10. Memory allocators and file systems

Allocators keep free blocks sorted by size or address in a balanced tree to find the best fit quickly. Some file systems use B-tree variants for directories and extents.

11. Timers and schedulers

A server with thousands of timeouts keeps them ordered by expiry time. A balanced tree (or a heap) gives the next timeout and lets timers be cancelled in O(log n).

12. Compilers and symbol tables

Some compilers and interpreters keep symbols in ordered maps so they can list names alphabetically or find the nearest match for an error suggestion.

Language standard libraries

Most languages already ship a sorted map or set built on a balanced tree. Check how yours works before writing your own. No mainstream library uses AVL specifically.

LanguageSorted map or setWhat it is under the hoodNotes
JavaTreeMap, TreeSetRed-black treeHas floorKey, ceilingKey and subMap for range queries.
C++std::map, std::setUsually a red-black treelower_bound and upper_bound for nearest-key and range queries.
C#SortedDictionary, SortedSetRed-black treeSortedSet has GetViewBetween for ranges.
RustBTreeMap, BTreeSetB-tree, not a binary treeUse range(a..b) for range queries. Rust's HashMap is unordered.
PythonNone built inThird-party sortedcontainers (SortedDict, SortedList)Not a tree, but gives the same ordered operations. For plain sorted lists, use the built-in bisect module.
GoNone built inThird-party packages, such as github.com/google/btreeGo's built-in map is a hash map with no ordering.
JavaScriptNone built inThird-party packagesMap keeps insertion order, not sorted order.
KotlinTreeMap, sortedMapOfRed-black tree (uses Java's)Same operations as Java.

The takeaway from the table: the languages that ship an ordered structure use a balanced tree for it. The ones that don't expect you to pull in a package, or to keep a sorted array and use binary search.

The takeaway

Whenever a system needs ordered data that keeps changing, look for a self-balancing tree such as an AVL, red-black or B-tree. When it only needs exact-match lookups, it usually uses a hash table instead.

Interview questions

Try answering out loud before you reveal hints. Interviewers care about how you reason, not just the final answer.

  1. 1.

    Check whether a binary tree is height-balanced (every node's subtrees differ in height by at most 1).

    easy
  2. 2.

    Convert a sorted array into a height-balanced BST.

    easy
  3. 3.

    Explain the four AVL rotation cases and draw the left-right case.

    medium
  4. 4.

    Why do standard libraries use red-black trees more than AVL trees?

    medium
  5. 5.

    Insert 10, 20, 30, 40, 50, 25 into an empty AVL tree. Name each rotation and draw the final tree.

    medium
  6. 6.

    How does a balanced tree answer "the smallest key at least x" and "all keys between a and b"?

    medium

Finished this lesson?

Progress is saved on this device. Create a free account to keep it everywhere.

Topics:TreesAvl treeRed black treeRotationsSelf balancingBinary search tree

↑ Back to top

Next lesson