Coding · Data structures
Binary search tree explained: how it works, code and complexity
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.
| Insert | Path taken | Placed as |
|---|---|---|
| 8 | empty tree | root |
| 3 | 8: smaller, go left | left child of 8 |
| 10 | 8: larger, go right | right child of 8 |
| 1 | 8 left, 3 left | left child of 3 |
| 6 | 8 left, 3 right | right child of 3 |
| 14 | 8 right, 10 right | right child of 10 |
| 4 | 8 left, 3 right, 6 left | left 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] 4Time and space complexity
| Operation | Balanced tree | Worst case (chain) | Reason |
|---|---|---|---|
| Search | O(log n) | O(n) | one comparison per level |
| Insert | O(log n) | O(n) | walk to a leaf position |
| Delete | O(log n) | O(n) | find the node, maybe find its successor |
| Min / max | O(log n) | O(n) | follow left (or right) links to the end |
| In-order traversal | O(n) | O(n) | every node once |
| Space | O(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.
| Variant | Balancing idea | Where it is used |
|---|---|---|
| AVL tree | heights of the two subtrees differ by at most 1 | lookup-heavy workloads |
| Red-black tree | color rules limit the longest path to twice the shortest | Java TreeMap, typical C++ std::map implementations, the Linux kernel |
| B-tree / B+ tree | many keys per node, all leaves at the same depth | databases and file systems |
| Treap, splay tree | random priorities, or moving accessed keys to the root | simpler 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.