DSA · Binary search trees
Binary search, built out of pointers
A BST spends one comparison per level, so a lookup costs the height of the tree and not the size of the data. You will insert seven values by hand, search one, and get all three delete cases right — including the two-children case that needs the inorder successor.
Descend a live tree, then delete a node →01 The idea
One comparison, one level down
A sorted array lets you binary-search, but inserting into the middle of it costs up to n shifts, because every later element has to move over. A binary search tree keeps the halving power and makes insertion cheap. It stores the order in links instead of in positions.
Each node holds a value and two links, called left and right. Stand on any node and one comparison settles everything: if your value is smaller it can only be in the left subtree, if it is bigger it can only be in the right one. You follow one link, and the entire other subtree drops out of the search. On a seven-node tree that takes the candidate count from 7 to 3 to 1 — the same three reads binary search needs on seven sorted values, done with pointers instead of indices.
02 Hand trace
Insert 5 3 8 1 4 7 9, then search for 4
Start with an empty tree and insert 5 3 8 1 4 7 9 in exactly that order. Every insert is a search that failed: you compare your way down, and the empty link you fall off is where the new node hangs. Each row below is one insert — the boxes are the nodes you compared against, and the green box is where the value landed.
Seven inserts, and this is the tree they build. Height 2, four leaves, and every level filled — the best shape seven nodes can take.
5 <- root, level 0 / \ 3 8 level 1 / \ / \ 1 4 7 9 level 2, all leaves
Now search for 4. Read the tree's values out in inorder — left subtree, node, right subtree — and you get 1 3 4 5 7 8 9, sorted, which is what inorder always does to a BST. The trace below lays those seven values in a row so you can watch the descent the way you watched a binary-search window. The rose box is the node you are standing on.
Three comparisons, and the candidate count went 7 → 3 → 1. That is not a coincidence. A balanced BST standing over a sorted sequence hands you the same middle elements binary search would pick, so it costs the same number of reads. What you pay for it is a pointer per link — and what you buy is that inserting 6 does not shift anything.
03 The code
One descent, and the three ways a delete ends
Search and insert are the same walk, so they share the code on the left. Delete is that walk plus one decision, and the decision is the part interviews probe: how many children does the doomed node have? There are three answers and each one restructures the tree differently.
Search — and insert, for free
node = rootwhile node is not null: if x == node.val: return FOUND if x < node.val: node = node.left else: node = node.rightnot found -> hang a new node holding x here
Delete — the three cases
find x, remembering its parentif x has no children: # case 1 cut the link from the parentelse if x has one child: # case 2 point the parent at that childelse: # case 3 s = leftmost node of x.right # successor x.val = s.val # copy it up delete s (it has no left child)
Why insert needs no logic of its own. Insert is a search that failed. You compare your way down exactly as search does, and the empty link you fall off is the only place the new value can sit without breaking the promise in section 01 — that is why the insert trace above is just seven searches. It also explains the cost: O(height) for a BST, against O(n) for a sorted array, where inserting 6 shifts 7, 8 and 9 over by one.
Why the inorder successor, and not any other node. Whatever value takes the deleted node's place has to be bigger than everything in its left subtree and smaller than everything in its right subtree. Exactly two values in the whole tree qualify: the largest in the left subtree (the inorder predecessor) and the smallest in the right (the successor). Anything else breaks the property somewhere below. Either one is correct — pick a side and stay consistent.
Why case 3 never turns into another case 3. The successor is the leftmost node of the right subtree, so by construction it has no left child. A node with no left child is a leaf or a one-child node, so the second delete is always case 1 or case 2. One delete is therefore one descent to the node plus one descent to the successor: still O(height).
The three cases, side by side
| Case | What you do | On our tree |
|---|---|---|
| 1 · no children | Cut the link from its parent. Nothing else in the tree moves. | Delete 1: the left link of 3 becomes empty. Inorder is now 3 4 5 7 8 9 — still sorted. |
| 2 · exactly one child | Point the parent straight at that child. The whole subtree under it slides up one level. | With 1 already gone, 3 has only the child 4. Delete 3: the left link of 5 now points at 4. |
| 3 · two children | Take the inorder successor — right once, then left as far as it goes. Copy its value into the node, then delete the successor. | Delete 5 from the full tree: go right to 8, left to 7, so 7 moves up and 8 loses its left child. |
Before · delete 5
5 <- two children / \ 3 8 / \ / \ 1 4 7 9 7 = successor
After · 7 moved up
7 <- successor is now the root / \ 3 8 / \ \ 1 4 9 8 lost its left child
05 Cheat sheet
Numbers worth memorising
| Case | Cost | What causes it |
|---|---|---|
| Search, insert, delete — balanced tree | O(log n) | One comparison per level, and a balanced tree has about log₂ n levels. Our 7-node tree has 3 levels, so at most 3 comparisons. |
| Search, insert, delete — skewed tree | O(n) | Insert 1 2 3 4 5 6 7 in that order and every node gets only a right child. The tree is a linked list with a wasted pointer per node. |
| Find the minimum or maximum | O(h) | Leftmost node for the minimum, rightmost for the maximum. Walk one direction until that link is null — no comparisons needed. Still one step per level, so O(log n) balanced and O(n) on a chain. |
| Inorder traversal (sorted output) | O(n) | Every node is printed once. This is optimal — you cannot emit n sorted values for less. |
| kth smallest, controlled inorder | O(h + k) | Inorder with a counter that stops at the kth value. Only the first k nodes are ever touched. |
| Space — recursion stack | O(h) | One frame per level for any recursive walk: O(log n) balanced, O(n) on a skewed tree. The iterative descent in section 03 keeps no stack at all — O(1). |
| Space — the tree itself | O(n) | n nodes, each carrying a value and two links. |
06 Where & why
When the order matters as much as the lookup
A BST is not the fastest way to answer "is x present". A hash table does that in O(1) and does not care about order at all. What the tree gives you is everything the hash table cannot: the value just below x, the value just above it, the kth smallest, a range, and the whole set in sorted order for the price of one walk.
Reach for it when…
Use something else when…
| Situation | Better choice | Why |
|---|---|---|
| You only ever ask "is x present", by exact key | Hash table / dictionary | O(1) average beats O(log n), and there is no ordering to maintain. |
| The data is fixed and you only read it | Sorted array + binary search | Same O(log n) lookups with no pointers, less memory and far better cache behaviour. |
| Keys can arrive sorted, or an attacker picks the order | Red-black or AVL tree (std::map, TreeMap, SortedDict) | They rotate on insert to hold the height at O(log n). A plain BST silently becomes a chain and every operation turns O(n). |
| You only ever need the smallest item, repeatedly | Binary heap | O(1) peek, a much smaller constant, and it never pays to keep the rest ordered. |
07 Interview questions
What interviewers actually ask
Eleven questions in the order an interview escalates: what it is, how fast, why that fast, then the delete and validate follow-ups that decide the round.
What is a binary search tree?
Why do you say search is O(h) and not O(log n)?
Which insert order gives the worst possible tree, and what does it cost?
Walk me through deleting a node that has two children.
Why the inorder successor specifically? Could you use some other node?
In the two-children case, can deleting the successor turn into another two-children case?
How do you check whether a binary tree is a valid BST?
What does an inorder traversal of a BST give you, and what does it cost?
How do you find the kth smallest value in a BST?
How do you find the floor of x — the largest value that is at most x?
BST or hash table — which do you actually reach for?
08 Practice problems
Six problems, in order of bite
Every one is solvable with what is above: the descent and the promise that makes it work, the height and what it costs, and the fact that an inorder walk of a BST comes out sorted.