Topics › Binary Trees › Traversals
Iterative Preorder Traversal of Binary Tree
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given the root of a binary tree, return its preorder traversal (the node, then its left subtree, then its right subtree) without using recursion. An empty tree gives an empty list.
Constraints: between 0 and 10^5 nodes, arbitrary 32-bit values, duplicates allowed. The tree may be a single chain 10^5 levels deep.
Trees are written in LeetCode’s level-order form, where null marks a missing child.
Example 1
1
/ \
2 3
/ \ / \
4 5 6 7
root = [1, 2, 3, 4, 5, 6, 7]
answer = [1, 2, 4, 5, 3, 6, 7]
Example 2
1
/ \
2 3
\ /
4 5
/
6
root = [1, 2, 3, null, 4, 5, null, null, null, 6]
answer = [1, 2, 4, 3, 5, 6] // 2 has only a right child, 3 only a left child
Why bother avoiding recursion? A recursive traversal uses one stack frame per level of the tree. On a balanced tree that is about log n frames, but on a chain it is n, and Java’s default thread stack overflows long before 10^5. An explicit stack lives on the heap and has no such limit. It also makes the traversal pausable, which is the basis of iterators over trees.
Approach 1: Recursion (the reference)
Intuition. Preorder is defined recursively: the root, then preorder of the left subtree, then preorder of the right subtree. This version is what the iterative methods must reproduce, and the tests compare them against it.
static List<Integer> recursive(TreeNode root) {
List<Integer> out = new ArrayList<>();
preorder(root, out);
return out;
}
private static void preorder(TreeNode node, List<Integer> out) {
if (node == null) return;
out.add(node.val); // Root
preorder(node.left, out); // Left
preorder(node.right, out); // Right
}
Complexity. Time O(n), space O(h) on the call stack, where h is the height.
It is worth seeing what the call stack actually stores. While the recursion is inside the left subtree of a node, the frame of that node is suspended at the line preorder(node.left, out), and the only work still owed to it is preorder(node.right, out). So the call stack is really a list of “right subtrees still to do”. The iterative versions keep that list themselves.
Approach 2: One stack, push right then left
Intuition. Keep a stack of subtrees that still have to be traversed, with the one that must be done next on top. Pop a node: in preorder the node itself comes first, so output it right away. What remains for this node is “left subtree, then right subtree”. Because the stack is last-in-first-out, push the right child first and the left child second; the left child is now on top and its entire subtree will be processed (everything it pushes lands above the waiting right child) before the right child is popped.
Algorithm.
- If the root is
null, return an empty list. - Push the root.
- While the stack is not empty: pop a node, append its value, push its right child if present, then its left child if present.
static List<Integer> iterativeStack(TreeNode root) {
List<Integer> out = new ArrayList<>();
if (root == null) return out;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
out.add(node.val); // visit as soon as the node is taken off
if (node.right != null) stack.push(node.right); // right waits underneath ...
if (node.left != null) stack.push(node.left); // ... left sits on top and is processed next
}
return out;
}
Dry run on Example 2. The stack is written bottom to top.
| Step | Popped | Pushed | Stack after | Output so far |
|---|---|---|---|---|
| 1 | 1 | 3, then 2 | [3, 2] | 1 |
| 2 | 2 | 4 (no left child) | [3, 4] | 1 2 |
| 3 | 4 | nothing | [3] | 1 2 4 |
| 4 | 3 | 5 (no right child) | [5] | 1 2 4 3 |
| 5 | 5 | 6 | [6] | 1 2 4 3 5 |
| 6 | 6 | nothing | [] | 1 2 4 3 5 6 |
Why it works. Invariant: the stack holds the roots of the subtrees that are still to be traversed, in the order they must be done from top to bottom, and the output so far is a prefix of the preorder. Popping a subtree root, outputting it, and replacing it with “left subtree, then right subtree” keeps that invariant, and the loop ends when nothing is left.
Complexity. Time O(n): each node is pushed and popped exactly once. Space O(h): the stack holds at most one pending right child for each node on the current path, plus the current left child.
Approach 3: Walk left, stack only the right children
Intuition. In Approach 2 the left child is pushed and then popped on the very next iteration, which is a wasted round trip. We can instead move to the left child directly and push only what genuinely has to wait: the right child. When the leftward walk falls off the tree, the most recent pending right subtree is the next thing to traverse.
This version has the same skeleton as the classic iterative inorder traversal (walk left, then resume from the stack), so learning it makes the other traversals easier to remember.
Algorithm.
- Set
cur = root. - While
curis notnullor the stack is not empty:- If
curisnull, pop the next pending right subtree intocur. - Append
cur, pushcur.rightif it exists, and move tocur.left.
- If
static List<Integer> iterativeRightStack(TreeNode root) {
List<Integer> out = new ArrayList<>();
Deque<TreeNode> pendingRight = new ArrayDeque<>();
TreeNode cur = root;
while (cur != null || !pendingRight.isEmpty()) {
if (cur == null) cur = pendingRight.pop(); // left path ended: resume the latest unexplored right subtree
out.add(cur.val);
if (cur.right != null) pendingRight.push(cur.right);
cur = cur.left;
}
return out;
}
Dry run on Example 2.
| Step | cur | Action | Pending stack | Output so far |
|---|---|---|---|---|
| 1 | 1 | output, push 3, go left | [3] | 1 |
| 2 | 2 | output, push 4, go left (null) | [3, 4] | 1 2 |
| 3 | null → 4 | pop 4, output, go left (null) | [3] | 1 2 4 |
| 4 | null → 3 | pop 3, output, go left | [] | 1 2 4 3 |
| 5 | 5 | output, go left | [] | 1 2 4 3 5 |
| 6 | 6 | output, go left (null) | [] | 1 2 4 3 5 6 |
At step 6 cur becomes null and the stack is empty, so the loop ends.
Complexity. Time O(n), each node visited once. Space O(h): the stack only ever holds right children of nodes on the current path. On a left-leaning tree it stays almost empty, while Approach 2 pushes and pops every node.
Comparing the three
| Recursion | Push right then left | Stack only right children | |
|---|---|---|---|
| Extra memory | call stack, O(h) |
heap stack, O(h) |
heap stack, O(h) |
Survives a 10^5-deep chain |
not reliably | yes | yes |
| Stack operations | none explicit | one push and pop per node | one push and pop per right child |
| Code shape | definition | “pop, emit, push children” | same as iterative inorder |
Edge cases and pitfalls
- Empty tree. Approach 2 must return before pushing, because
ArrayDequedoes not acceptnull. Approach 3 never enters its loop. - Push order. Pushing left before right gives Root, Right, Left. Reversed, that is postorder, which is a handy trick but not what is asked here.
- Missing children. Push a child only when it exists. Pushing
nullthrows inArrayDeque, and with ajava.util.Stackit would only move the crash tonode.val. - Use
ArrayDeque.java.util.Stackis a synchronised legacy class built onVector;Dequewithpush,popandpeekis the modern stack. - Deep trees. The tests build chains of
10^5nodes iteratively, in both directions, and run both iterative methods on them.
Related problems
- Preorder Traversal covers the recursive version and the
O(1)-space Morris variant. - Iterative Inorder Traversal of Binary Tree reuses the walk-left-then-pop skeleton of Approach 3.
- Post-order Traversal of Binary Tree using 2 stack mirrors Approach 2 and reverses the result.
- Post-order Traversal of Binary Tree using 1 stack is the hardest of the iterative traversals.
- Pre, Post, Inorder in one traversal keeps a state per stack frame to get all three orders at once.
- Morris Preorder Traversal of a Binary Tree removes the stack entirely.
- Flatten Binary Tree to Linked List rearranges a tree into its preorder sequence.