cv.cm

Coding · Data structures

Binary search tree explained: how it works, code and complexity

6 min read

Short answer

A binary search tree (BST) is a binary tree where every node's left subtree holds only smaller keys and its right subtree holds only larger keys. Because of this rule, search, insert and delete follow a single path from the root and take O(h) time, where h is the tree height: O(log n) when the tree is balanced, but O(n) when it degenerates into a chain. An in-order traversal visits the keys in sorted order.

What problem a binary search tree solves

A sorted array gives fast binary search, but inserting a new element shifts everything after it. A hash table gives fast lookup and insert, but keeps no order, so it cannot answer "what is the smallest key larger than 42?" or "list all keys between 10 and 20". A binary search tree keeps keys sorted and still allows fast inserts and deletes. It is the basis of ordered maps and sets such as Java's TreeMap and C++'s std::map, which use self-balancing variants.

The rule in plain words

Each node has a key, an optional left child and an optional right child. For every node: all keys in its left subtree are smaller, and all keys in its right subtree are larger. To find a key, start at the root and compare: go left if the key is smaller, go right if it is larger, stop if it is equal or if you fall off the tree.

Worked example: building and searching a tree

Insert 8, 3, 10, 1, 6, 14, 4 in this order. Each new key walks down from the root and becomes a leaf where it falls off.

InsertPath takenPlaced as
8empty treeroot
38: smaller, go leftleft child of 8
108: larger, go rightright child of 8
18 left, 3 leftleft child of 3
68 left, 3 rightright child of 3
148 right, 10 rightright child of 10
48 left, 3 right, 6 leftleft child of 6

Searching for 4 compares with 8, 3, 6, then 4: four comparisons in a tree of seven keys. Searching for 7 compares with 8, 3, 6 and then finds no right child of 6, so 7 is not present.

Python implementation

Insert and search are short loops. Delete has three cases: the node has no children (remove it), one child (replace it with the child), or two children (copy in its in-order successor, the smallest key of the right subtree, then delete that successor).

class Node:
    def __init__(self, key):
        self.key, self.left, self.right = key, None, None
def insert(root, key):
    if root is None:
        return Node(key)
    if key < root.key:
        root.left = insert(root.left, key)
    elif key > root.key:
        root.right = insert(root.right, key)
    return root                      # duplicates are ignored
def contains(root, key):
    while root and root.key != key:
        root = root.left if key < root.key else root.right
    return root is not None
def delete(root, key):
    if root is None:
        return None
    if key < root.key:
        root.left = delete(root.left, key)
    elif key > root.key:
        root.right = delete(root.right, key)
    else:
        if root.left is None:
            return root.right
        if root.right is None:
            return root.left
        succ = root.right
        while succ.left:
            succ = succ.left
        root.key = succ.key
        root.right = delete(root.right, succ.key)
    return root
def inorder(root):
    return inorder(root.left) + [root.key] + inorder(root.right) if root else []
root = None
for k in [8, 3, 10, 1, 6, 14, 4]:
    root = insert(root, k)
print(inorder(root))                 # [1, 3, 4, 6, 8, 10, 14]
print(contains(root, 4), contains(root, 7))  # True False
root = delete(root, 3)               # node with two children
print(inorder(root), root.left.key)  # [1, 4, 6, 8, 10, 14] 4

Time and space complexity

OperationBalanced treeWorst case (chain)Reason
SearchO(log n)O(n)one comparison per level
InsertO(log n)O(n)walk to a leaf position
DeleteO(log n)O(n)find the node, maybe find its successor
Min / maxO(log n)O(n)follow left (or right) links to the end
In-order traversalO(n)O(n)every node once
SpaceO(n)O(n)one node per key; recursion uses O(h) stack

The worst case is real, not theoretical: inserting already sorted keys (1, 2, 3, ...) into a plain BST gives every node only a right child, and the tree becomes a linked list. Random insertion orders give an expected height of O(log n).

Balanced variants and common mistakes

Self-balancing trees keep the height at O(log n) by restructuring with rotations after inserts and deletes.

VariantBalancing ideaWhere it is used
AVL treeheights of the two subtrees differ by at most 1lookup-heavy workloads
Red-black treecolor rules limit the longest path to twice the shortestJava TreeMap, typical C++ std::map implementations, the Linux kernel
B-tree / B+ treemany keys per node, all leaves at the same depthdatabases and file systems
Treap, splay treerandom priorities, or moving accessed keys to the rootsimpler balanced trees, caches
  • Validating a BST by checking only parent and child. Pass down a valid (low, high) range instead.
  • Forgetting to return the (possibly new) root from recursive insert and delete, so changes are lost.
  • Deep recursion on a degenerate tree: Python's default recursion limit is about 1000, so a chain of 5000 nodes crashes recursive code. Use loops or keep the tree balanced.
  • Undefined duplicate policy: decide whether duplicates are ignored, counted, or always go to one side.

When to use a BST and typical interview questions

Use a balanced BST when you need ordered data with frequent inserts and deletes: range queries, floor and ceiling lookups, rank statistics, or iterating in sorted order. Use a hash table when you only need exact-key lookup. In Python, the standard library has no tree map; common choices are a sorted list with the bisect module for small or mostly static data, or the third-party sortedcontainers package.

Typical interview questions: validate a BST, find the k-th smallest element (in-order traversal), lowest common ancestor, convert a sorted array to a balanced BST (pick the middle as the root), and find the in-order successor of a node.

FAQ

What is the difference between a binary tree and a binary search tree?

A binary tree only limits each node to two children. A binary search tree adds the ordering rule: smaller keys on the left, larger keys on the right, for every subtree.

Why is BST search O(log n)?

Each comparison moves one level down and discards one subtree. A balanced tree with n nodes has about log2(n) levels, so search needs about that many comparisons.

What happens if I insert sorted data into a BST?

Without balancing, each new key becomes the right child of the previous one, the height becomes n, and every operation becomes O(n). Self-balancing trees such as AVL and red-black trees prevent this.

How do you get the keys of a BST in sorted order?

Do an in-order traversal: visit the left subtree, then the node, then the right subtree. It takes O(n) time.

Does Python have a built-in binary search tree?

No. Python has dict and set (hash tables) and the bisect module for sorted lists. Ordered tree containers come from third-party packages.

All coding tutorials