Kth Smallest Element in a BST
Interview Prep
The problem
Given the root of a binary search tree and an integer
k ≥ 1, return the k-th smallest value in
the tree.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
self.size = 1 # only needed for the followup Pattern: in-order, stop on k-th visit
A BST's in-order traversal yields values in sorted order. So the
k-th smallest is the k-th value emitted by an in-order walk. The
iterative version uses an explicit stack and lets us short-circuit
as soon as the counter hits zero — visiting O(h + k)
nodes instead of the full O(n).
Iterative solution
def kth_smallest(root: Node, k: int) -> int:
"""In-order traversal. Stop on the k-th visit."""
stack = []
node = root
while stack or node:
while node:
stack.append(node)
node = node.left
node = stack.pop()
k -= 1
if k == 0:
return node.val
node = node.right Recursive (for clarity)
def kth_smallest_recursive(root, k):
out = []
def walk(n):
if n is None or len(out) >= k: return
walk(n.left)
if len(out) < k: out.append(n.val)
walk(n.right)
walk(root)
return out[-1] Trace
Tree: 5
/ \
3 6
/ \
2 4
/
1
In-order order: 1, 2, 3, 4, 5, 6.
k=3 -> answer 3.
Iterative walk (stack):
push 5, push 3, push 2, push 1. pop 1 -> visit (k=2). go right (None).
pop 2 -> visit (k=1). go right (None).
pop 3 -> visit (k=0). return 3. Complexity
- Time:
O(h + k)with the iterative early-exit version. - Space:
O(h)stack.
Follow-up: many queries on a frequently-modified BST
Interviewers love this follow-up: "what if the BST gets inserts and
deletes, and we need many k-th-smallest queries?". The
answer is to augment each node with the
size of its subtree. With sizes maintained on insert/delete
(one extra increment per affected node), the k-th smallest can be
found in O(h) — descending left when k
falls inside the left subtree, descending right (with
k −= left_size + 1) otherwise.
def kth_smallest_augmented(root: Node, k: int) -> int:
"""If each node knows its subtree SIZE, k-th smallest is O(h) — log n on a balanced BST.
For repeated queries on a BST that's being mutated, this is the right structure.
"""
while root:
left_size = root.left.size if root.left else 0
if k == left_size + 1:
return root.val
if k <= left_size:
root = root.left
else:
k -= left_size + 1
root = root.right Why augmentation pays off
Without augmentation, an insert is O(log n) but a
k-th-smallest query is O(h + k) — slow when
k is large. With augmentation, both are
O(log n) on a balanced BST, and the size maintenance
is essentially free (one increment per node touched during insert).
This is the canonical example of an order-statistics
tree.
Variations worth seeing
- Kth largest: reverse in-order (right subtree first), same logic.
- Rank of a value (inverse query): with subtree sizes, descend toward the value while accumulating the count of everything to its left.
- Median of a BST: two queries with
k = ⌊n/2⌋andk = ⌈n/2⌉; average if needed.