Traversals
On This Lesson 11 topics
Lesson Overview
A list has one natural way to visit every element: from the first to the last. A tree has no single "next" element, because every node can have two children. If you want to print, count, copy, or search every node, you must choose an order. Those orders are called traversals.
This lesson teaches four standard ones. Three are short recursive methods that differ only in when the current node is handled: pre-order, in-order, and post-order. The fourth, level-order, visits the tree row by row and uses a queue instead of recursion. You will see that in-order on a binary search tree produces the values in sorted order, which is the reason the BST is useful beyond lookups.
Why this matters: nearly every real tree task is a traversal in disguise. Listing a folder's contents, serializing a comment thread, evaluating an expression, and exporting a sorted report from an ordered structure are all of this shape.
This lesson builds on Lesson 1 (recursion over Node), Lesson 2 (the BST), and on Queue and ArrayDeque from the collections module.
Learning Objectives
By the end of this lesson you will be able to:
- Write pre-order, in-order, and post-order traversals recursively, each with the
nullbase case. - Write level-order traversal with a queue.
- Produce all four outputs for a drawn tree by hand.
- Explain why in-order on a BST gives sorted values.
- Say what each order is good for.
- State the cost of any traversal and explain why the number of nodes decides it.
Written Explanation
The idea: visit, then recurse
A traversal handles ("visits") every node exactly once. For each node there are three things to do: visit the node itself, traverse its left subtree, traverse its right subtree. The three recursive traversals differ only in where the visit goes. Left is always done before right.
| Traversal | Order | Memory aid |
|---|---|---|
| Pre-order | node, left, right | the node comes pre (before) its subtrees |
| In-order | left, node, right | the node comes in the middle |
| Post-order | left, right, node | the node comes post (after) its subtrees |
"Visit" here means "add the value to a list". The methods below take a list to fill. We use the tree built by inserting 8, 3, 10, 1, 6, 14:
8
/ \
3 10
/ \ \
1 6 14static void preOrder(Node n, List<Integer> out) {
if (n == null) return;
out.add(n.value); // visit first
preOrder(n.left, out);
preOrder(n.right, out);
}
static void inOrder(Node n, List<Integer> out) {
if (n == null) return;
inOrder(n.left, out);
out.add(n.value); // visit in the middle
inOrder(n.right, out);
}
static void postOrder(Node n, List<Integer> out) {
if (n == null) return;
postOrder(n.left, out);
postOrder(n.right, out);
out.add(n.value); // visit last
}The three methods are identical except for the line out.add(...). The null check comes first in each, as always.
Tracing in-order by hand
For the tree above, inOrder(8) first runs inOrder(3). That runs inOrder(1), which has no children and adds 1. Back in 3: add 3, then inOrder(6) adds 6. Back in 8: add 8, then inOrder(10), which has no left child, adds 10, then goes right and adds 14. The result is 1, 3, 6, 8, 10, 14.
That list is sorted. It is not luck. By the BST rule, everything in a node's left subtree is smaller than the node, and everything in the right subtree is larger. In-order visits the whole left subtree, then the node, then the whole right subtree, so at every node the values come out in increasing order. If the tree is not a BST, the in-order output is still a valid traversal, but it is not sorted.
Level-order: row by row
Level-order visits the root, then all nodes one link below, then all nodes two links below, and so on, going left to right in each row. It cannot be written as a short recursion, because it jumps between subtrees. Instead it uses a queue, first in, first out, which you met in the collections module.
The idea: put the root in the queue. Repeatedly take the front node, visit it, and add its children to the back. Because children are added behind everything already waiting, a whole row is finished before the next row begins.
static List<Integer> levelOrder(Node root) {
List<Integer> out = new ArrayList<>();
if (root == null) return out;
Queue<Node> queue = new ArrayDeque<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node n = queue.poll();
out.add(n.value);
if (n.left != null) queue.offer(n.left);
if (n.right != null) queue.offer(n.right);
}
return out;
}Here the null checks are on the children, because a Queue created by ArrayDeque does not accept null elements. The empty tree is handled at the top. This traversal is also called breadth-first, because it goes across before it goes down. The three recursive ones are depth-first: they go all the way down one path first.
Cost
Every traversal visits each node once and does a constant amount of work per visit, so the cost is O(n) for a tree of n nodes, whatever the shape. Level-order also needs extra memory for the queue, at most the width of the widest row. The recursive versions use stack space equal to the tree's height, because each pending call is a frame on the call stack.
Examples
Example 1: all four orders by hand (simple)
For the tree above (8, 3, 10, 1, 6, 14), the four outputs are:
| Order | Output |
|---|---|
| Pre-order | 8 3 1 6 10 14 |
| In-order | 1 3 6 8 10 14 |
| Post-order | 1 6 3 14 10 8 |
| Level-order | 8 3 10 1 6 14 |
To check pre-order: visit 8, then go into the left subtree (3, then 1, then 6), then the right subtree (10, then 14). To check post-order: finish 3's subtree (1, 6, 3), then 10's subtree (14, 10), and the root comes last. The root is the first item of pre-order and the last item of post-order.
Example 2: an expression tree (practical)
A tree can describe a calculation. The tree for (2 + 3) * 4 has * at the root, with + as its left child (holding 2 and 3) and 4 as its right child. Leaves hold numbers, and internal nodes hold operators.
static void pre(N n, StringBuilder sb) {
if (n == null) return;
sb.append(n.s).append(' ');
pre(n.l, sb);
pre(n.r, sb);
}
static void post(N n, StringBuilder sb) {
if (n == null) return;
post(n.l, sb);
post(n.r, sb);
sb.append(n.s).append(' ');
}Here N is a small node class with a String s and two children. Running both on the tree (compiled and run with JDK 21):
pre : * + 2 3 4
post: 2 3 + 4 *The post-order result, 2 3 + 4 *, lists the operands before their operator, the form that calculators based on a stack can evaluate without parentheses: push numbers, and when you see an operator, pop two numbers and push the result. Post-order is also the natural order for deleting a tree in languages without garbage collection, because children must go before the parent. In Java you rarely need that, but it shows why the order matters.
Example 3: all four traversals on a BST, plus a sorted proof (realistic)
The main method below builds the BST from the lesson and prints every traversal. Then it builds a second tree from the same values in a different insertion order.
public static void main(String[] args) {
Node root = null;
for (int v : new int[] {8, 3, 10, 1, 6, 14}) root = insert(root, v);
List<Integer> pre = new ArrayList<>(), in = new ArrayList<>(), post = new ArrayList<>();
preOrder(root, pre);
inOrder(root, in);
postOrder(root, post);
System.out.println("pre " + pre);
System.out.println("in " + in);
System.out.println("post " + post);
System.out.println("level " + levelOrder(root));
List<Integer> empty = new ArrayList<>();
inOrder(null, empty);
System.out.println("empty " + empty + " " + levelOrder(null));
Node other = null;
for (int v : new int[] {14, 10, 8, 6, 3, 1}) other = insert(other, v);
List<Integer> in2 = new ArrayList<>(), pre2 = new ArrayList<>();
inOrder(other, in2);
preOrder(other, pre2);
System.out.println("in2 " + in2);
System.out.println("pre2 " + pre2);
}Output (compiled and run with JDK 21):
pre [8, 3, 1, 6, 10, 14]
in [1, 3, 6, 8, 10, 14]
post [1, 6, 3, 14, 10, 8]
level [8, 3, 10, 1, 6, 14]
empty [] []
in2 [1, 3, 6, 8, 10, 14]
pre2 [14, 10, 8, 6, 3, 1]The first four lines match the hand-made table. The second tree has the same values but a different shape, so its pre-order is different, yet the in-order output is identical. Sorted output does not depend on how the tree was built. The tree built from 14, 10, 8, 6, 3, 1 leans entirely to the left, which previews the next lesson.
Backend Insight
- Sorted export. Any ordered tree can produce a sorted listing with one in-order pass. When you iterate over a
TreeMaporTreeSet, the JDK walks the tree in this order, so you get the keys in ascending order without sorting. Compare that with aHashMap, whose iteration order you must not rely on. - Hierarchies. Printing a category menu with sub-items indented, or rendering a nested comment thread, is a pre-order traversal: show the parent, then its children. Computing the total size of a folder is a post-order traversal: you need the children's sizes before you can add up the parent's.
- Breadth versus depth. "Show me everything within two levels of this category" is a level-order job. The same queue-based idea returns in the graph module as breadth-first search, where it finds the fewest hops between two points.
- Deep recursion. The recursive traversals use one stack frame per level of depth. A very tall tree (Lesson 4) can overflow the call stack, which is why production code for unbalanced data sometimes uses an explicit stack or queue.
Common Mistakes
Mistake 1: Missing null checks
A traversal that omits if (n == null) return; fails with a NullPointerException the first time it reaches a leaf's missing child. In levelOrder, adding a null child to the queue fails as well.
Mistake 2: Putting the visit in the wrong place
Pre-, in-, and post-order are the same code with the out.add line moved. If your "in-order" output is not sorted on a BST, check whether the line sits between the two recursive calls.
Mistake 3: Expecting sorted output from the other orders
Only in-order gives sorted values on a BST. Pre-order starts with the root and post-order ends with it, so neither is sorted in general. Level-order is not sorted either.
Mistake 4: Using recursion for level-order
Level-order needs a queue. A recursive version that simply goes left then right produces a depth-first order. If your result lists the whole left branch before the right one, you are doing depth-first.
Exercises
Try each exercise yourself before opening the solutions.
Use this tree, built by inserting 40, 20, 60, 10, 30, 50, 70, 25 in that order.
40
/ \
20 60
/ \ / \
10 30 50 70
/
25Level A: Foundation
A1. Write the pre-order and post-order outputs.
Show solution A1
A1. Pre-order: 40 20 10 30 25 60 50 70. Post-order: 10 25 30 20 50 70 60 40.
A2. Write the in-order output. What do you notice about it, and why?
Show solution A2
A2. In-order: 10 20 25 30 40 50 60 70. It is sorted, because the tree is a BST and in-order visits each node's smaller values before it and larger values after it.
A3. Which traversal gives the root first, and which gives it last?
Show solution A3
A3. Pre-order gives the root first. Post-order gives it last.
Level B: Application
B1. Write the level-order output, and then list what the queue contains right after the root's children have been added (before 20 is visited).
Show solution B1
B1. Level-order: 40 20 60 10 30 50 70 25. After 40 is visited, the queue holds 20 and 60 (in that order, with 20 at the front). Children go to the back, so 20 is visited before 60, and rows stay together.
B2. Modify inOrder so the values come out in descending order. Show the result for the tree above.
Show solution B2
B2. Visit the right subtree first, then the node, then the left subtree:
static void descending(Node n, List<Integer> out) {
if (n == null) return;
descending(n.right, out);
out.add(n.value);
descending(n.left, out);
}Result: 70 60 50 40 30 25 20 10 (compiled and run with JDK 21).
B3. You want to compute the total size of every folder in a file system tree. Which traversal fits, and why?
Show solution B3
B3. Post-order. A folder's size is the sum of its children's sizes plus its own files, so the children must be finished before the parent is computed.
Level C: Challenge
C1. Write levels(Node root) that returns a List<List<Integer>>, one inner list per row. Hint: at the start of each round, remember how many nodes the queue holds.
Show solution C1
C1.
static List<List<Integer>> levels(Node root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<Node> q = new ArrayDeque<>();
q.offer(root);
while (!q.isEmpty()) {
int count = q.size(); // nodes in this row
List<Integer> row = new ArrayList<>();
for (int i = 0; i < count; i++) {
Node n = q.poll();
row.add(n.value);
if (n.left != null) q.offer(n.left);
if (n.right != null) q.offer(n.right);
}
result.add(row);
}
return result;
}On the tree above it returns [[40], [20, 60], [10, 30, 50, 70], [25]] (compiled and run with JDK 21).
C2. A BST's pre-order output is 50, 30, 20, 40, 70, 60. Draw the tree. Explain why inserting the values in that order rebuilds it.
Show solution C2
C2. The tree: root 50; left child 30 with children 20 and 40; right child 70 with a left child 60. Pre-order lists a node before everything in its subtrees, so every node appears after its ancestors. When you insert in that order, each value finds its parent already in place and attaches below it by the BST rule, so the same tree results.
Mini Challenge
Take the tree you drew in the Mini Challenge of Lesson 2 (six names). Write the four traversals for it by hand. Then predict, before running anything, which one will list the names alphabetically. If you want to check yourself in code, adapt the inOrder method for String values.
Check your work: each traversal lists exactly six names, in-order is alphabetical, pre-order starts with the root, and post-order ends with it.
Summary & Next Lesson
Key takeaways
- A traversal visits every node once. Pre-order is node, left, right; in-order is left, node, right; post-order is left, right, node.
- In-order on a BST returns the values in sorted order.
- Level-order visits row by row and uses a queue instead of recursion.
- Each recursive traversal starts with the
nullcheck; level-order checks children before adding them to the queue. - Every traversal costs O(n), because each node is visited once.
Next lesson
The next lesson, "Balance (concept only)", asks what happens when values arrive already sorted: the BST becomes a chain, and its fast searches turn into slow ones.
Self-check
- Which traversal prints a BST in sorted order?
- What is the first item of a pre-order traversal?
- Which data structure does level-order use?
- What is the cost of any traversal of n nodes?
- What is the difference between pre-order and post-order code?
Show answers
- In-order.
- The root.
- A queue.
- O(n).
- Only the position of the line that visits the node: before the recursive calls for pre-order, after them for post-order.
References
- OpenDSA (Virginia Tech): Binary Tree Traversals. It defines pre-order, in-order, and post-order, says in-order on a BST yields ascending values, and mentions post-order for deleting a tree. It does not cover level-order or give running times, so those parts of this lesson follow the module's own outline and the one-visit-per-node argument.
- OpenDSA (Virginia Tech): Binary Search Trees. It states that in-order traversal visits BST nodes in ascending order and costs Θ(n) whatever the shape.
- Every Java example above was compiled and run with JDK 21 and the output is copied from the real run.
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.