Trees · Binary Search Tree Operations

BST Iterator Design

Simulating a full recursive inorder traversal one step at a time using an explicit stack that only ever holds a root-to-current-node's ancestors' left spine, so "give me the next value" costs amortized O(1) instead of needing the whole traversal computed upfront.

  • 5 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: descending, with peek
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

A caller wants a BST's values in sorted order, one at a time, through next() and hasNext(), and may stop after a few. Recursive inorder produces everything in one go and can't pause. Building the full sorted list works, but it costs O(n) even if the caller only wants three values. This skill builds an iterator that does only the work each call needs: an explicit stack that holds one path of the tree at a time.

We'll go from simple to more technical:

  1. The pattern: an ascending iterator built on a stack of the left spine.
  2. A second flavor: descending order, plus peek().
  3. Where these iterators run in production (sorted maps, database cursors, generators).
  4. What breaks it: forgotten right subtrees, a peek that advances, and precomputing everything.

The approach

Java

class BSTIterator {
    private final Deque<TreeNode> stack = new ArrayDeque<>();

    BSTIterator(TreeNode root) { pushLeft(root); }

    private void pushLeft(TreeNode node) {
        while (node != null) { stack.push(node); node = node.left; }
    }

    boolean hasNext() { return !stack.isEmpty(); }

    int next() {
        TreeNode node = stack.pop();       // the smallest value not yet returned
        pushLeft(node.right);              // everything in its right subtree comes next, smallest first
        return node.val;
    }
}

Python

class BSTIterator:
    def __init__(self, root: TreeNode | None):
        self.stack = []                  # a Python list is the stack: append / pop
        self.push_left(root)

    def push_left(self, node: TreeNode | None) -> None:
        while node is not None:
            self.stack.append(node)
            node = node.left

    def has_next(self) -> bool:
        return bool(self.stack)

    def next(self) -> int:
        node = self.stack.pop()          # the smallest value not yet returned
        self.push_left(node.right)       # everything in its right subtree comes next, smallest first
        return node.val

This is recursive inorder with the call stack made explicit and paused between calls. The stack holds the nodes whose left subtree is being (or has been) handled but whose own value hasn't been returned yet - the top is always the smallest value still pending. Popping it and pushing its right child's left spine is exactly the work recursion would do next.

A second flavor: descending, with peek

Mirroring left and right gives the values largest first: push the right spine, and after popping a node push its left child's right spine. Adding peek() - "what's next, without advancing" - is one line, because the next value is always on top of the stack:

Java

class DescendingIterator {
    private final Deque<TreeNode> stack = new ArrayDeque<>();
    DescendingIterator(TreeNode root) { pushRight(root); }
    private void pushRight(TreeNode node) { for (; node != null; node = node.right) stack.push(node); }
    boolean hasNext() { return !stack.isEmpty(); }
    int peek() { return stack.peek().val; }                  // look, don't advance
    int next() { TreeNode node = stack.pop(); pushRight(node.left); return node.val; }
}

Python

class DescendingIterator:
    def __init__(self, root: TreeNode | None):
        self.stack = []
        self.push_right(root)

    def push_right(self, node: TreeNode | None) -> None:
        while node is not None:
            self.stack.append(node)
            node = node.right

    def has_next(self) -> bool:
        return bool(self.stack)

    def peek(self) -> int:
        return self.stack[-1].val        # look, don't advance

    def next(self) -> int:
        node = self.stack.pop()
        self.push_right(node.left)
        return node.val

An ascending and a descending iterator together are the O(h)-space version of the two-pointer sweep (see Two Sum in a BST).

Where you'll see it

Every sorted-map iterator (TreeMap.keySet().iterator(), a database cursor scanning an index) works this way: it keeps just enough state to produce the next item and advances only when asked, so a caller that stops early pays only for what it read. The same "explicit stack, paused traversal" shape is what generators and lazy sequences compile down to. In Python you can write it as one - a recursive inorder(node) that does yield from inorder(node.left), yield node.val, yield from inorder(node.right) pauses at every yield - but a generator can't answer has_next() or peek() without pulling the next value early, which is why the explicit stack is still the interview answer.

Which tool for which job?

You needUse
Sorted values on demand, O(h) spacea stack of the left spine; next() pops and pushes the right child's left spine
Largest firstthe mirror: right spines
peek()the top of the stack, without popping
O(1) worst case per call, memory no concernprecompute the inorder list
A quick lazy loop in Pythonan iterative generator (no has_next or peek)
Changes during iterationfail fast like TreeMap, or iterate a snapshot

Common mistakes

MistakeWhat happensDo this instead
Not pushing the popped node's right subtreestops early: 3, 7 and donepushLeft(node.right) after each pop
Pushing only the right childits left subtree is never returnedpush the right child's whole left spine
peek() that popsthe next next() skips a valueread the top, don't pop
Precomputing everything in the constructorO(n) time and space for a caller that wants three valuesthe lazy stack
Changing the tree mid-iterationstale or missing valuesfail fast, or a snapshot

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. A single next() call can push several nodes. Why is next() still O(1) amortized?

Show answer

Each node is pushed exactly once (when the left spine it's on gets pushed) and popped exactly once (when it's returned). Over a full iteration of n values that's 2n stack operations in total, so the average per next() is constant, even though an individual call may push up to h nodes.

2. next() pops a node and returns it without pushing anything. On [7, 3, 15, null, null, 9, 20], what does the iterator produce?

Show answer

3, then 7, then hasNext() is false. The constructor pushed only the left spine (7, 3). Nothing ever pushes 15's subtree, so 9, 15 and 20 are lost.

3. What changes to make the iterator run in descending order?

Show answer

Swap left and right everywhere: the constructor pushes the right spine, and after popping a node, push the right spine of its left child. The top of the stack is then always the largest value not yet returned.

Key takeaways

  • A BST iterator is inorder traversal with the stack made explicit, advanced one value per next().
  • Push the left spine; next() pops the top and pushes its right child's left spine.
  • O(h) space; next() is amortized O(1) because every node is pushed and popped once.
  • Mirror it (right spine, then the left child's right spine) for a descending iterator; peek() is just the top of the stack.
  • Precomputing the list gives O(1) per call but O(n) space and up-front work - wasteful when the caller stops early.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Ascending, on demandstack of the left spine
Descendingstack of the right spine
peek()top of the stack
O(1) worst case per callprecomputed list (O(n) memory)
No stackparent pointers, or Morris traversal

Snippets worth knowing by heart

Java

int next() {
    TreeNode node = stack.pop();
    for (TreeNode c = node.right; c != null; c = c.left) stack.push(c);
    return node.val;
}

Python

def inorder(node):                       # iterative generator: lazy, no recursion limit
    stack = []
    while stack or node:
        while node:
            stack.append(node)
            node = node.left
        node = stack.pop()
        yield node.val
        node = node.right

The traps

  • Forgetting the right subtree after a pop: iteration stops early. Call pushLeft(node.right) after each pop.
  • Pushing only the right child: its left subtree is never returned. Push the right child's whole left spine.
  • A peek() that advances: the next next() skips a value. Read the top without popping.
  • Precomputing everything costs O(n) for a caller who wants three values. Keep the lazy stack.
  • Mutating the tree mid-iteration: values go stale or missing. Fail fast, or iterate a snapshot.

Staff-level points, one line each

  • 65,535 keys: the first 3 values took 17 pushes; a full iteration took 65,535 pushes, at most 15 in one call, with a peak stack of 16.
  • A recursive yield from generator on a 900-deep chain was at least 10x slower than the stack.
  • A stale stack returned 16,382 values from a subtree that had already been removed.
  • TreeMap: putting a new key mid-loop threw ConcurrentModificationException; removing through the iterator and replacing values were fine.
  • TreeMap iterates with parent pointers, not a stack.

Say it in an interview

  • "It's inorder with the recursion stack made explicit and paused."
  • "Space is O(h). next() is amortized O(1), because each node is pushed and popped once."
  • "peek is just the top of the stack."
  • "If the tree can change, I'd fail fast with a modification count."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice BST Iterator Design free

BST Iterator

MediumProCoding problemJava · Python

Design an iterator over a binary search tree that returns node values in ascending (inorder) order. next() returns the next smallest value; hasNext() returns whether a next value exists. Both should run in amortized O(1) time and O(h) space, where h is the tree's height.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Descending BST Iterator With Peek

MediumProCoding problemJava · Python

Design an iterator over a binary search tree that returns its values in descending order:

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice BST Iterator Design free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime