Topics › Binary Trees › Traversals
Preorder Traversal
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given the root of a binary tree, return the values of its nodes in preorder: first the node itself, then its entire left subtree in preorder, then its entire right subtree in preorder. An empty tree (a null root) has an empty traversal.
Constraints: the number of nodes is between 0 and 10^5 here (LeetCode uses at most 100), and values are arbitrary 32-bit integers. Duplicate values are allowed; the traversal is about nodes, not values.
Trees in the examples are written in LeetCode’s level-order form, where null marks a missing child.
Example 1
1
\
2
/
3
root = [1, null, 2, 3]
answer = [1, 2, 3] // 1 first; its left subtree is empty; then the subtree rooted at 2
Example 2
1
/ \
2 3
/ \ / \
4 5 6 7
root = [1, 2, 3, 4, 5, 6, 7]
answer = [1, 2, 4, 5, 3, 6, 7] // the whole left subtree (2, 4, 5) comes before anything on the right
The name tells you where the root goes: preorder puts the root before its subtrees, inorder puts it between them, postorder puts it after. Preorder is the natural order for copying a tree or writing it out, because every parent is produced before its children, so a reader can rebuild the tree top-down.
Approach 1: Recursion
Intuition. A binary tree is defined recursively: it is either empty, or a root with a left subtree and a right subtree, each of which is again a binary tree. Preorder follows the same shape: the preorder of a tree is the root, followed by the preorder of the left subtree, followed by the preorder of the right subtree. Code that mirrors this definition is three lines long.
One practical detail: instead of having each call return a new list and concatenating them (which copies elements again at every level, up to O(n^2) work on a skewed tree), we pass one shared output list down and let every call append to it.
Algorithm.
- If the current node is
null, return; an empty subtree contributes nothing. - Append the node’s value.
- Traverse the left subtree.
- Traverse the right subtree.
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; // empty subtree: nothing to visit
out.add(node.val); // Root
preorder(node.left, out); // Left
preorder(node.right, out); // Right
}
Why it works. By induction on the size of the subtree: a call on an empty subtree appends nothing, which is correct. For a larger subtree, the call appends the root, then (by the induction hypothesis) exactly the left subtree’s nodes in preorder, then exactly the right subtree’s nodes in preorder. That is the definition.
Complexity. Time O(n): every node is the argument of exactly one call that does constant work, plus n + 1 calls on null children that return at once. Space O(h) for the call stack, where h is the height: about log n for a balanced tree but n for a skewed one. Java’s default thread stack typically fits somewhere in the tens of thousands of frames like these, so a chain of 10^5 nodes can throw StackOverflowError. That is the main reason interviewers ask for the iterative versions below.
Approach 2: Iterative with a stack
Intuition. Recursion uses the call stack to remember work that is still pending. We can keep that memory ourselves in an explicit stack. In preorder the node is output the moment we take it; after that, the work left for this node is “the left subtree, then the right subtree”. A stack is last-in-first-out, so we push the right child first and the left child second. The left child is popped next, and everything it pushes lands on top of the waiting right child, so the whole left subtree is finished before the right child surfaces.
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> iterative(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);
if (node.right != null) stack.push(node.right); // pushed first, popped last
if (node.left != null) stack.push(node.left); // pushed last, popped next
}
return out;
}
Dry run on Example 2. The stack is written bottom to top, so the right end is the top.
| Step | Popped | Output so far | Stack after the pushes |
|---|---|---|---|
| 1 | 1 | 1 | [3, 2] |
| 2 | 2 | 1 2 | [3, 5, 4] |
| 3 | 4 | 1 2 4 | [3, 5] |
| 4 | 5 | 1 2 4 5 | [3] |
| 5 | 3 | 1 2 4 5 3 | [7, 6] |
| 6 | 6 | 1 2 4 5 3 6 | [7] |
| 7 | 7 | 1 2 4 5 3 6 7 | [] |
Notice that node 3 waits at the bottom for the entire left subtree. That is exactly the “come back later for the right side” information that recursion keeps in its stack frames.
Complexity. Time O(n): each node is pushed once and popped once. Space O(h): at any moment the stack holds the current node’s left child plus at most one waiting right child for each ancestor on the current path.
Use ArrayDeque as the stack. The old java.util.Stack class extends Vector, synchronises every call and is kept only for backward compatibility.
Approach 3: Morris traversal
Intuition. Both previous approaches need O(h) memory to find the way back up from a left subtree to its parent. Morris traversal stores that “way back” inside the tree itself, using pointers that are null anyway. Before descending into the left subtree of cur, find the rightmost node of that left subtree (in inorder it is the node visited just before cur, called its predecessor). Its right pointer is null, so we temporarily point it at cur. This link is called a thread. When the walk later finishes the left subtree, it follows the thread back to cur, sees that the predecessor already points at cur, removes the thread, and moves to the right subtree.
Every node with a left child is therefore reached twice. For preorder we output it on the first arrival, before going left. Nodes without a left child are reached once and output immediately.
Algorithm.
- Set
cur = root. - While
curis notnull:- If
curhas no left child, output it and move tocur.right(which may be a thread back up). - Otherwise let
predbe the rightmost node ofcur.left, stopping early ifpred.right == cur.- If
pred.rightisnull(first arrival): outputcur, setpred.right = cur, move tocur.left. - Otherwise (second arrival): set
pred.right = nullto restore the tree, move tocur.right.
- If
- If
static List<Integer> morris(TreeNode root) {
List<Integer> out = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
out.add(cur.val); // no left subtree: visit and go right
cur = cur.right;
} else {
TreeNode pred = cur.left; // rightmost node of the left subtree
while (pred.right != null && pred.right != cur) pred = pred.right;
if (pred.right == null) { // first arrival: visit, leave a thread back, go left
out.add(cur.val);
pred.right = cur;
cur = cur.left;
} else { // second arrival: left subtree done, remove the thread
pred.right = null;
cur = cur.right;
}
}
}
return out;
}
Complexity. Time O(n). The predecessor searches look expensive, but each one walks down the right edges of one left subtree, and every edge of the tree is walked by at most two such searches (once to create a thread, once to remove it). Space O(1) besides the output list.
The price is that the tree is temporarily modified. That is unsafe if another thread reads the tree at the same time, and if the loop were interrupted half-way the tree would be left with threads in it. The test in the Java file runs the recursive traversal again after Morris to confirm the tree was restored.
Edge cases and pitfalls
- Empty tree. All three methods return an empty list. The iterative version must check for a
nullroot before pushing, becauseArrayDeque.push(null)throwsNullPointerException. - Single node. The answer is that one value; no stack ever holds more than one element.
- Push order. Pushing the left child before the right one produces Root, Right, Left. That is not preorder, although its reverse happens to be postorder, a trick used for iterative postorder.
- Skewed trees. A chain of
10^5nodes overflows the default call stack in the recursive version. The tests run the iterative and Morris versions on such chains, in both directions. - Duplicates and negative values. Nothing changes: we never compare values, only follow references.
[5, -3, 8, -3, 0]gives[5, -3, -3, 0, 8]. - Morris loop condition. The predecessor search must stop when
pred.right == cur. Without that check it follows the thread back tocurand loops forever.
Related problems
- Inorder Traversal of Binary Tree moves the root between the two subtrees.
- Postorder Traversal moves the root after both subtrees.
- Iterative Preorder Traversal of Binary Tree studies the stack version in more depth, including a variant that stacks only right children.
- Pre, Post, Inorder in one traversal produces all three orders from a single walk.
- Morris Preorder Traversal of a Binary Tree covers the threading idea in full detail.
- Level Order Traversal visits the tree breadth-first instead of depth-first.
- Serialize and De-serialize BT writes a tree out in preorder with markers for missing children.