Closest Element in a BST is a classic warm-up for thinking with the binary-search-tree invariant. Instead of scanning every node, you let the tree's order steer you straight toward the target — visiting at most one node per level.
Problem. Given the root of a binary search tree and a target value, return the value in the
tree that is closest to target (the smallest absolute difference). If two values tie, returning
either is fine.
Example: the BST has values [2, 4, 6, 8, 10, 12, 14] and target = 9 → answer 8 (because |8 − 9| = 1, and no other value is closer).
The slow way first
The obvious idea: walk every node, compute |node.val - target| for each, and keep the minimum. That works for any tree and is O(n) — you touch all n nodes.
But this is a BST, and we are throwing away its biggest gift: order. At any node, every value to the left is smaller and every value to the right is larger. So once we compare with a node, we already know which side the closer value must be on. There is no reason to ever look at the other side.
The idea: step toward the target
Keep a running closest candidate. Start at the root and repeat: compare the current node, update closest if this node is nearer, then move left if the target is smaller and right if it is larger. When you walk off the bottom (a child is None), stop.
The key insight: each comparison eliminates an entire subtree, so the path length is the tree height, not the node count. On a balanced BST that is O(log n).
Walk through it
Step through the animation with target = 9. The node pointer rides the current node. We start at root 8 (diff 1, our best). 9 > 8, so we turn right to 12 (diff 3, worse — keep 8). 9 < 12, so we turn left to 10 (diff 1, a tie — we keep 8). 9 < 10, but 10 has no left child, so the loop ends. Answer: 8, after visiting only 3 of the 7 nodes.
Pseudocode
closest = root.val
node = root
while node is not None:
if |node.val - target| < |closest - target|:
closest = node.val
if target > node.val:
node = node.right # closer value can only be larger
else:
node = node.left # closer value can only be smaller
return closestThe Python solution
def closest_value(root, target):
closest = root.val
node = root
while node:
if abs(node.val - target) < abs(closest - target):
closest = node.val
if target > node.val:
node = node.right
else:
node = node.left
return closestcloseststarts at the root value so we always have a candidate to compare against.- The
while nodeloop walks a single root-to-leaf path; each turn drops the half of the tree we ruled out. - Line 5 is the candidate test — we only replace
closeston a strictly smaller difference, so ties keep the earlier value. - Lines 7–10 are the steering:
target > node.valmeans the closer value is to the right, otherwise left. - When
nodebecomesNone, the loop ends and we return the best value found.
Complexity
| Case | Time | Notes |
|---|---|---|
| Scan every node | O(n) (moderate) | ignores the BST order |
| Walk one path (balanced) | O(log n) (fast) | one node per level |
| Walk one path (skewed) | O(n) (moderate) | height equals node count |
O(1) (fast)We use only O(1) extra space — a couple of variables, no recursion stack (the loop is iterative). The runtime is O(height): great on a balanced tree, and no worse than the brute force on a degenerate one.
When this pattern shows up
Whenever a problem hands you a BST and asks for something positional — closest value, floor/ceil, k-th element, a value in a range — the move is the same: at each node, compare and then turn left or right. You almost never need to visit both children.
Use a strict less-than when updating the candidate (< , not <=). With <= you would still get
a correct closest value, but if the prompt says return the smaller value on a tie, the loose comparison
could overwrite it with a later equal one.
Practice
Starting at root 8 with target 9, after we move right to 12, which way do we turn next and why?
1. Why can this run in O(log n) on a balanced BST?
2. When the target is greater than the current node value, which way do we move?
3. Why initialize closest to the root value?
4. What extra space does the iterative solution use?