HARD Lab · Trees · 71

230. Kth Smallest Element in a BST

An in-order traversal of a binary search tree emits its values in sorted order, so the k-th node you visit is the k-th smallest — stop the moment a running counter hits k.

MediumInorder TraversalBSTTypeScript

PROBLEM What we're solving

Given the root of a binary search tree and an integer k (1-indexed), return the k-th smallest value in the tree. For root = [3,1,4,null,2] the sorted order is 1, 2, 3, 4, so k = 1 1 and k = 3 3.

KEY IDEA In-order of a BST is sorted

Insight → for any BST, an in-order traversal (left → node → right) visits values in strictly increasing order. So you never need to collect and sort — just count nodes as you visit them in-order and return the k-th one. Because you can stop the instant the counter reaches k, you only ever touch the smallest k nodes plus the spine above them.

RECIPE Iterative in-order with an explicit stack

  • 0 · Set up. An empty stack and a cursor cur = root. The stack will hold ancestors whose left side we've already descended.
  • 1 · Dive left. While curisn't null, push it and move to cur.left. This stacks the left spine so the smallest unvisited node ends up on top.
  • 2 · Visit (pop). Pop the top node — it's the next-smallest. Do --k; if it hits 0, this node is the answer.
  • 3 · Go right. Otherwise set cur = node.right and loop. Repeat until you return.
Classic confusion → the stack does not hold the in-order sequence — it holds the chain of ancestors waiting to be visited. The sorted order appears only in theorder you pop. Also note the visit happens on pop, not on push; pushing is just "remember to come back here."

COST Complexity & alternatives

Collect all + sort
O(n log n)
Traverse everything, then sort the values.
In-order, stop at k
O(H + k)
Only the smallest k nodes plus the spine (H = height).

Space & variants

The explicit stack holds at most H nodes, so space is O(H)O(log n) for a balanced tree, O(n)in the worst (degenerate) case. A recursive in-order with a counter is equally valid; the iterative version just makes the "stop early" logic explicit.

Pattern transfer → iterative in-order with a stack is the workhorse behind Validate BST (check each popped value is larger than the last), BST Iterator (this exact loop, paused between next() calls), and Recover BST (spot the two out-of-order pops).

RUN IT In-order walk with an explicit stack — stop at the k-th visit

step 0 / 3
STARTIn-order traversal of a BST visits values in sorted order. We walk left as far as possible, then pop & visit, then go right. The k-th value we visit is the answer (k = 1).
State
stack top
0
count
1
k
just visitedstack topcount < kcount === k (answer)
slowfast

TYPESCRIPT The solution, annotated

kthSmallest.ts
class TreeNode {
  val: number;
  left: TreeNode | null;
  right: TreeNode | null;
  constructor(val: number, left: TreeNode | null = null, right: TreeNode | null = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

function kthSmallest(root: TreeNode | null, k: number): number {
  const stack: TreeNode[] = [];
  let cur: TreeNode | null = root;

  while (cur !== null || stack.length > 0) {
    // Walk the left spine: smaller values go on top.
    while (cur !== null) {
      stack.push(cur);
      cur = cur.left;
    }
    // Pop the next-smallest unvisited node.
    cur = stack.pop()!;
    if (--k === 0) return cur.val;   // k-th in-order node
    // Its left subtree is done; explore the right subtree.
    cur = cur.right;
  }

  throw new Error('k is larger than the number of nodes');
}

Reading it block by block

Setup. stack holds ancestors we still owe a visit; cur is the node we're currently descending from. The outer loop runs while either there is a node to descend (cur) or unvisited ancestors remain (stack.length).
Inner while — dive left. Push cur and keep moving to cur.left. This loads the entire left spine onto the stack so the smallest unvisited value sits on top.
Pop = visit. stack.pop() yields the next value in sorted order. --k decrements first; when it reaches 0 we've visited exactly k nodes, so cur.val is the answer.
Go right.The popped node's left subtree is fully done, so the only unseen values are in its right subtree. Set cur = cur.right and the loop will dive its left spine next.
Fallthrough. If the loop ends without returning, k exceeded the node count — an invalid input we guard with a throw.
Complexity → Time O(H + k): at most H pushes to reach the smallest node, then k pop/visit steps. Space O(H) for the stack — O(log n) balanced, O(n) worst case.

INTERVIEWFollow-ups they'll ask

  • "What if the BST is modified often and you need many queries?" Augment each node with the size of its left subtree; then each query is O(H) by comparing k to that size at each step.
  • "Recursive version?" In-order recursion with a shared counter and an early return / exception once k hits zero — same O(H + k).
  • "k-th largest instead?" Do a reverse in-order (right → node → left), which visits values in descending order.
  • "Prove in-order is sorted." By the BST invariant every left descendant is smaller and every right descendant larger than a node, so left → node → right emits them ascending — provable by induction on subtree height.

MNEMONIC The one-liner

"Dive left, pop to visit, count down k, then go right."

TRIGGERS When you see ___ → reach for ___

"k-th smallest in a BST"in-order traversal + counter
need sorted order from a BSTleft → node → right
stop as soon as you caniterative in-order, return on --k === 0
"k-th largest"reverse in-order (right → node → left)

SKELETON The reusable shape

skeleton.ts
const stack: TreeNode[] = [];
let cur = root;
while (cur || stack.length) {
  while (cur) { stack.push(cur); cur = cur.left; }
  cur = stack.pop()!;
  if (--k === 0) return cur.val;
  cur = cur.right;
}

FLASHCARDS Tap to flip

Why does in-order give the answer directly?
In-order traversal of a BST visits values in sorted (ascending) order, so the k-th visited node is the k-th smallest.
tap to flip
1 / 6
Answer to reveal explanations. No penalty for retries.Score: 0 / 7
QUESTION 01
Why is in-order traversal the natural choice here?
QUESTION 02
In the iterative version, a node is "visited" (counted toward k) when it is:
QUESTION 03
For root = [3,1,4,null,2], what is the 2nd smallest (k = 2)?
QUESTION 04
After popping and visiting a node (without hitting k), the cursor moves to:
QUESTION 05
Best-case time when you can stop early (vs. collecting all values and sorting)?
QUESTION 06
What does the explicit stack hold during the traversal?
QUESTION 07
If the BST is modified frequently and you must answer many k-th-smallest queries fast, the best upgrade is: