Intuition
Inorder traversal of a BST emits values in sorted order — so the k-th visited node is the answer. Traverse iteratively and stop early; no need to materialize the whole list.
In-order traversal of a BST emits values in sorted order — that single fact turns "k-th smallest" into "stop after k emissions". Doing it with an explicit stack rather than recursion is what lets you stop early instead of walking the whole tree, which matters when k is small or the follow-up asks about frequent queries.
Approach
Inorder = sorted stream
Left, node, right yields ascending values by the BST property.
Iterate with an explicit stack
Push the left spine; pop = next smallest; then walk the popped node's right subtree's left spine.
Stop at k
Decrement k per pop; return when it hits zero — O(h + k) work.
Solution & live demo
Common pitfalls
Collecting the full in-order list first
vals = []
def dfs(n):
if not n: return
dfs(n.left); vals.append(n.val); dfs(n.right)
dfs(root)
return vals[k - 1]while True:
while node: stack.append(node); node = node.left
node = stack.pop()
k -= 1
if k == 0: return node.val
node = node.rightCorrect, but it visits all n nodes and allocates all n values to answer a question that's settled after k. The iterative form stops the instant the count runs out — O(h + k) instead of O(n).
Decrementing k before popping
while node:
stack.append(node)
k -= 1
node = node.leftnode = stack.pop() k -= 1 if k == 0: return node.val
Pushing is not visiting — nodes go onto the stack in descending order down the left spine, long before their turn. A node is only visited in sorted position when it comes off the stack.
Going left again after popping
node = stack.pop() node = node.left
node = stack.pop() ... node = node.right
The left subtree was already fully consumed on the way down — that's why this node is next in order. Revisiting it loops forever; the unexplored part is the right subtree.
Edge cases
Traversal ends exactly on the maximum node.
Augment nodes with subtree sizes → O(h) per query (LeetCode's follow-up).