Facebook Pixel

173. Binary Search Tree Iterator

Problem Description

You need to implement a BSTIterator class that allows you to iterate through a Binary Search Tree (BST) in in-order traversal sequence (left → root → right).

The class should have three main components:

  1. Constructor BSTIterator(TreeNode root): Takes the root of a BST and initializes the iterator. The internal pointer should start at a position before the smallest element (conceptually at a "non-existent" position smaller than any element in the tree).

  2. Method next(): Returns the next smallest number in the BST and advances the pointer. Since the pointer starts before the first element, the first call to next() will return the smallest element in the BST.

  3. Method hasNext(): Returns true if there are more elements to iterate through (elements to the right of the current pointer position), and false otherwise.

The key requirements are:

  • The iteration should follow in-order traversal order, which for a BST means visiting nodes in ascending order of their values
  • You can assume that next() will only be called when there are remaining elements (when hasNext() returns true)
  • The iterator should efficiently traverse through all nodes exactly once

Example usage pattern:

obj = BSTIterator(root)
while obj.hasNext():
    value = obj.next()  # Gets next value in ascending order

The solution shown uses a preprocessing approach where it performs a complete in-order traversal during initialization to store all values in a list self.vals. It then uses a cursor self.cur to track the current position in this list. The next() method returns the value at the current cursor position and increments it, while hasNext() checks if the cursor has reached the end of the list.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to iterate through a BST in ascending order, we're essentially asking for an in-order traversal. The natural question is: how can we provide elements one at a time on demand, rather than all at once?

The most straightforward approach is to perform the complete in-order traversal upfront and store all values in a list. This way, we transform the tree traversal problem into a simple array iteration problem. Think of it like reading a book - instead of searching through scattered pages each time, we arrange all pages in order first, then simply flip through them one by one.

Why does this work well? In-order traversal of a BST naturally gives us elements in sorted (ascending) order. By collecting all values during initialization:

  • We do the complex tree navigation work once
  • The next() operation becomes a simple array access with O(1) time
  • The hasNext() check is just comparing indices with O(1) time

The trade-off here is space - we're using O(n) extra space to store all node values. However, this makes the iterator operations extremely simple and efficient. We maintain a cursor (self.cur) that starts at 0 and moves forward with each next() call, mimicking how we'd iterate through a regular list.

This approach is like preparing a playlist before a party - you spend time upfront organizing the songs in order, but then playing the next song is instant. The alternative would be searching through your music library each time you need the next song, which would be more complex and potentially slower for each operation.

Solution Approach

The solution uses a preprocessing strategy with in-order traversal to collect all BST values during initialization. Here's the step-by-step implementation:

1. Initialization Phase (__init__ method)

The constructor sets up three key components:

  • self.cur = 0: A cursor to track our current position in the traversal
  • self.vals = []: A list to store all BST values in sorted order
  • A nested inorder function that performs recursive in-order traversal

The inorder function follows the classic recursive pattern:

def inorder(root):
    if root:
        inorder(root.left)    # Visit left subtree
        self.vals.append(root.val)  # Process current node
        inorder(root.right)   # Visit right subtree

This traversal visits nodes in the exact order we need: left → root → right, which for a BST means ascending order. After initialization, self.vals contains all values sorted from smallest to largest.

2. Next Element Retrieval (next method)

This method is straightforward:

def next(self) -> int:
    res = self.vals[self.cur]  # Get value at current position
    self.cur += 1              # Move cursor forward
    return res

Since we've already arranged values in order, getting the next element is just array indexing at position self.cur, then incrementing the cursor for the next call.

3. Checking for More Elements (hasNext method)

The check is a simple boundary condition:

def hasNext(self) -> bool:
    return self.cur < len(self.vals)

If the cursor hasn't reached the end of our values list, there are more elements to iterate through.

Time and Space Complexity

  • Initialization: O(n) time to traverse all n nodes, O(n) space for the recursion stack and storing values
  • next(): O(1) time - simple array access
  • hasNext(): O(1) time - simple comparison

The total space complexity is O(n) for storing all node values, which trades memory for the simplicity and speed of iterator operations.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with a small BST example:

       4
      / \
     2   6
    / \   \
   1   3   7

Step 1: Initialization When we create BSTIterator(root), the constructor:

  • Sets self.cur = 0 (cursor at start position)
  • Creates empty list self.vals = []
  • Performs in-order traversal:
    • Visit node 1: vals = [1]
    • Visit node 2: vals = [1, 2]
    • Visit node 3: vals = [1, 2, 3]
    • Visit node 4: vals = [1, 2, 3, 4]
    • Visit node 6: vals = [1, 2, 3, 4, 6]
    • Visit node 7: vals = [1, 2, 3, 4, 6, 7]

After initialization:

  • self.vals = [1, 2, 3, 4, 6, 7] (all values in ascending order)
  • self.cur = 0 (pointing to index 0)

Step 2: First hasNext() call

  • Check: cur (0) < len(vals) (6) → Returns true

Step 3: First next() call

  • Get vals[0] = 1
  • Increment cursor: cur = 1
  • Return 1

Step 4: Second next() call

  • Get vals[1] = 2
  • Increment cursor: cur = 2
  • Return 2

Step 5: Continue iterating...

  • next() returns 3, cursor becomes 3
  • next() returns 4, cursor becomes 4
  • next() returns 6, cursor becomes 5
  • next() returns 7, cursor becomes 6

Step 6: Final hasNext() call

  • Check: cur (6) < len(vals) (6) → Returns false
  • No more elements to iterate!

The iterator successfully returned all BST values in ascending order: 1, 2, 3, 4, 6, 7.

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8class BSTIterator:
9    """
10    Iterator for Binary Search Tree that returns values in ascending order.
11    Uses in-order traversal to flatten the BST into a list during initialization.
12    """
13  
14    def __init__(self, root: TreeNode) -> None:
15        """
16        Initialize the iterator with the root of a BST.
17        Performs in-order traversal to store all values in sorted order.
18      
19        Args:
20            root: The root node of the binary search tree
21        """
22        # Initialize current index pointer and values list
23        self.current_index = 0
24        self.values = []
25      
26        # Perform in-order traversal to populate values list
27        self._inorder_traversal(root)
28  
29    def _inorder_traversal(self, node: TreeNode) -> None:
30        """
31        Helper method to perform in-order traversal of the BST.
32        Populates the values list with node values in ascending order.
33      
34        Args:
35            node: Current node being processed
36        """
37        if node:
38            # Traverse left subtree
39            self._inorder_traversal(node.left)
40            # Process current node
41            self.values.append(node.val)
42            # Traverse right subtree
43            self._inorder_traversal(node.right)
44  
45    def next(self) -> int:
46        """
47        Returns the next smallest element in the BST.
48      
49        Returns:
50            The next smallest value in the BST
51        """
52        # Get the value at current index
53        result = self.values[self.current_index]
54        # Move pointer to next element
55        self.current_index += 1
56        return result
57  
58    def hasNext(self) -> bool:
59        """
60        Checks if there are more elements to iterate.
61      
62        Returns:
63            True if there are more elements, False otherwise
64        """
65        return self.current_index < len(self.values)
66
67
68# Your BSTIterator object will be instantiated and called as such:
69# obj = BSTIterator(root)
70# param_1 = obj.next()
71# param_2 = obj.hasNext()
72
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16
17/**
18 * Binary Search Tree Iterator implementation.
19 * Performs in-order traversal of BST and stores values in a list.
20 * Provides iterator functionality to traverse through BST values in ascending order.
21 */
22class BSTIterator {
23    // Current index pointer for iteration through the values list
24    private int currentIndex;
25  
26    // List to store all BST values in sorted order (in-order traversal result)
27    private List<Integer> values;
28
29    /**
30     * Constructor initializes the iterator with the root of BST.
31     * Performs complete in-order traversal and stores all values.
32     * Time Complexity: O(n) where n is the number of nodes
33     * Space Complexity: O(n) for storing all node values
34     * 
35     * @param root The root node of the binary search tree
36     */
37    public BSTIterator(TreeNode root) {
38        this.currentIndex = 0;
39        this.values = new ArrayList<>();
40        performInorderTraversal(root);
41    }
42
43    /**
44     * Returns the next smallest element in the BST.
45     * Time Complexity: O(1)
46     * 
47     * @return The next smallest integer value in the BST
48     */
49    public int next() {
50        // Return current value and increment the index for next call
51        return values.get(currentIndex++);
52    }
53
54    /**
55     * Checks if there are more elements to iterate.
56     * Time Complexity: O(1)
57     * 
58     * @return true if there are more elements, false otherwise
59     */
60    public boolean hasNext() {
61        // Check if current index is within the bounds of values list
62        return currentIndex < values.size();
63    }
64
65    /**
66     * Helper method to perform in-order traversal of the BST.
67     * Recursively traverses left subtree, processes current node, then right subtree.
68     * This ensures values are added to the list in ascending order.
69     * 
70     * @param node The current node being processed in the traversal
71     */
72    private void performInorderTraversal(TreeNode node) {
73        // Base case: if node is null, return
74        if (node == null) {
75            return;
76        }
77      
78        // Traverse left subtree first (smaller values)
79        performInorderTraversal(node.left);
80      
81        // Process current node - add its value to the list
82        values.add(node.val);
83      
84        // Traverse right subtree (larger values)
85        performInorderTraversal(node.right);
86    }
87}
88
89/**
90 * Your BSTIterator object will be instantiated and called as such:
91 * BSTIterator obj = new BSTIterator(root);
92 * int param_1 = obj.next();
93 * boolean param_2 = obj.hasNext();
94 */
95
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class BSTIterator {
13private:
14    std::vector<int> values;      // Stores all BST values in sorted order
15    int currentIndex;              // Current position in the values vector
16  
17public:
18    /**
19     * Constructor: Initialize the iterator with the root of BST
20     * Performs inorder traversal to populate values in sorted order
21     * @param root: The root node of the binary search tree
22     */
23    BSTIterator(TreeNode* root) {
24        currentIndex = 0;
25        performInorderTraversal(root);
26    }
27  
28    /**
29     * Returns the next smallest number in the BST
30     * @return: The value at current position and advances the iterator
31     */
32    int next() {
33        return values[currentIndex++];
34    }
35  
36    /**
37     * Checks if there are more elements to iterate
38     * @return: true if there are more elements, false otherwise
39     */
40    bool hasNext() {
41        return currentIndex < values.size();
42    }
43  
44private:
45    /**
46     * Helper function to perform inorder traversal of BST
47     * Populates the values vector with node values in sorted order
48     * @param node: Current node being processed
49     */
50    void performInorderTraversal(TreeNode* node) {
51        if (node != nullptr) {
52            // Traverse left subtree
53            performInorderTraversal(node->left);
54          
55            // Process current node
56            values.push_back(node->val);
57          
58            // Traverse right subtree
59            performInorderTraversal(node->right);
60        }
61    }
62};
63
64/**
65 * Your BSTIterator object will be instantiated and called as such:
66 * BSTIterator* obj = new BSTIterator(root);
67 * int param_1 = obj->next();
68 * bool param_2 = obj->hasNext();
69 */
70
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15// Array to store the in-order traversal values
16let inorderValues: number[] = [];
17// Current position in the iterator
18let currentIndex: number = 0;
19
20/**
21 * Initializes the BST iterator with the root of the BST
22 * Performs in-order traversal to flatten the tree into an array
23 * @param root - The root node of the binary search tree
24 */
25function BSTIterator(root: TreeNode | null): void {
26    // Reset the state for new iterator instance
27    currentIndex = 0;
28    inorderValues = [];
29  
30    /**
31     * Helper function to perform in-order traversal (left -> root -> right)
32     * This ensures values are stored in ascending order for BST
33     * @param node - Current node being processed
34     */
35    const performInorderTraversal = (node: TreeNode | null): void => {
36        // Base case: if node is null, return
37        if (node === null) {
38            return;
39        }
40      
41        // Traverse left subtree first
42        performInorderTraversal(node.left);
43        // Process current node by adding its value to the array
44        inorderValues.push(node.val);
45        // Traverse right subtree last
46        performInorderTraversal(node.right);
47    };
48  
49    // Start the in-order traversal from root
50    performInorderTraversal(root);
51}
52
53/**
54 * Returns the next smallest element in the BST
55 * @returns The next smallest value in the iterator
56 */
57function next(): number {
58    // Return the value at current index and increment the index
59    return inorderValues[currentIndex++];
60}
61
62/**
63 * Checks if there are more elements to iterate
64 * @returns True if there are more elements, false otherwise
65 */
66function hasNext(): boolean {
67    // Check if current index is within bounds of the array
68    return currentIndex < inorderValues.length;
69}
70
71/**
72 * Your BSTIterator object will be instantiated and called as such:
73 * BSTIterator(root)
74 * var param_1 = next()
75 * var param_2 = hasNext()
76 */
77

Time and Space Complexity

Time Complexity:

  • __init__: O(n) where n is the number of nodes in the BST. The inorder traversal visits each node exactly once to build the values array.
  • next(): O(1) as it simply accesses an element from the array by index and increments the cursor.
  • hasNext(): O(1) as it only performs a simple comparison between the cursor and array length.

Space Complexity:

  • Overall: O(n) where n is the number of nodes in the BST.
    • The self.vals array stores all n values from the tree.
    • The recursive inorder traversal uses O(h) space for the call stack, where h is the height of the tree. In the worst case (skewed tree), h = n, and in the best case (balanced tree), h = log(n).
    • Since we're storing all values in the array, the dominant space complexity is O(n).

Note: This implementation trades space for simplicity by pre-computing all values during initialization. An alternative approach using a stack could achieve O(h) average space complexity by computing values on-demand during iteration.

Common Pitfalls

1. Memory Inefficiency for Large Trees

The preprocessing approach stores all n values upfront, which can be problematic for very large BSTs where memory is constrained. If the BST contains millions of nodes but you only need to iterate through a few elements, you're wasting significant memory.

Solution: Use a lazy evaluation approach with a stack to store only the path to the current node:

class BSTIterator:
    def __init__(self, root: TreeNode):
        self.stack = []
        self._push_all_left(root)
  
    def _push_all_left(self, node):
        while node:
            self.stack.append(node)
            node = node.left
  
    def next(self) -> int:
        node = self.stack.pop()
        if node.right:
            self._push_all_left(node.right)
        return node.val
  
    def hasNext(self) -> bool:
        return len(self.stack) > 0

This approach uses O(h) space where h is the height of the tree, instead of O(n).

2. Handling Empty Trees

The current solution works correctly for empty trees (when root is None), but developers might forget to test this edge case or might add unnecessary null checks.

What to verify:

  • Empty tree should result in hasNext() returning False immediately
  • The values list remains empty, so the cursor logic still works

3. Thread Safety Issues

If multiple threads try to use the same iterator instance concurrently, the shared current_index variable can lead to race conditions where:

  • Two threads might get the same value from next()
  • The index might skip values or go out of bounds

Solution: Either:

  • Document that the iterator is not thread-safe
  • Add synchronization locks around state modifications
  • Create separate iterator instances for each thread

4. Incorrect Assumption About BST Properties

Developers might assume the BST is balanced or has specific properties. The solution works for any valid BST structure (balanced or unbalanced), but performance characteristics differ:

  • Balanced BST: Stack approach uses O(log n) space
  • Skewed BST: Stack approach uses O(n) space in worst case

Always consider worst-case scenarios when analyzing complexity.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More