Mastering Binary Tree Traversal: A Beginner's Guide
Pre-order, in-order, post-order, and level-order traversal explained with simple mental models and practical code patterns.
Abstract Algorithms
TLDR: Binary tree traversal is about visiting every node in a controlled order. Learn pre-order, in-order, post-order, and level-order, and you can solve many interview and production problems cleanly.
๐ Four Ways to Walk a Tree โ and Why the Order Matters
A production file-system scanner had a bug: it was performing in-order traversal on a directory tree and visiting every directory twice โ once when descending into it and once when returning. A routine 2-minute backup job stretched into a 40-minute one. The fix was three lines of code: changing the traversal order so each directory was processed exactly once, at the right point in the recursion. But you can only write that fix if you understand what in-order traversal actually does โ and when not to use it.
Binary tree traversal is the mental model behind that kind of debugging. Given this tree:
A
/ \
B C
/ \
D E
The four traversals produce completely different visit sequences:
- Pre-order (Root โ Left โ Right):
A B D E Cโ visits each node before its children. - In-order (Left โ Root โ Right):
D B E A Cโ visits left subtree, then node, then right. - Post-order (Left โ Right โ Root):
D E B C Aโ visits all children before the parent. - Level-order (BFS):
A B C D Eโ visits every node at each depth before going deeper.
Same tree, four different visit sequences. The order you choose determines what you can compute efficiently.
Think of visiting floors in a building:
- Pre-order: announce the current floor name before walking down to lower floors.
- In-order: walk all the way to the lowest left room first, then announce floors on the way back up.
- Level-order: walk every room on floor 1 before touching floor 2.
| Traversal | Visit order | Common use |
| Pre-order | Root โ Left โ Right | Serialize/copy a tree |
| In-order | Left โ Root โ Right | Sorted output from a BST |
| Post-order | Left โ Right โ Root | Delete or free nodes safely |
| Level-order | Top to bottom by level | Shortest path, BFS problems |
๐ Traversal Foundations: DFS vs. BFS
Before memorising the four traversal names, it helps to understand the two families they belong to.
Depth-First Search (DFS) dives as deep as possible along one branch before backtracking. Pre-order, in-order, and post-order are all DFS variants โ they differ only in when they record the current node relative to their recursive calls. DFS uses the call stack (recursion) or an explicit stack for memory, so its space cost scales with the tree's height.
Breadth-First Search (BFS) fans out level by level, visiting every node at depth 1 before any node at depth 2. Level-order traversal is BFS applied to a tree. BFS uses a queue (first-in, first-out), so its space cost scales with the widest level in the tree.
Understanding which family a traversal belongs to immediately tells you its memory characteristic and which problem shapes it fits.
| Property | DFS (Pre / In / Post) | BFS (Level-order) |
| Data structure | Stack (call stack or explicit) | Queue |
| Memory (balanced tree) | O(log n) | O(n) at the widest level |
| Memory (skewed tree) | O(n) โ risky for deep trees | O(1) โ only one node per level |
| Finds deepest node first? | Yes | No โ finds shallowest leaf first |
| Sorted BST output? | In-order only | No |
| Best for level-by-level work? | No | Yes |
Rule of thumb: if a problem mentions "level", "depth", or "shortest path", reach for BFS. If it mentions "sorted output", "parent before children", or "children before parent", reach for DFS.
๐ DFS vs. BFS: Two Families of Tree Traversal
flowchart TD
A[Tree Traversal] --> B{Strategy?}
B -- Depth First --> C[DFS]
B -- Level by Level --> D[BFS / Level Order]
C --> E{Order?}
E -- Left-Root-Right --> F[Inorder]
E -- Root-Left-Right --> G[Preorder]
E -- Left-Right-Root --> H[Postorder]
D --> I[Use Queue]
This flowchart maps the two families of tree traversal โ DFS and BFS โ to their concrete strategies. Starting from the root decision, the DFS branch splits into three variants (Inorder, Preorder, Postorder) determined solely by when the current node is recorded relative to its recursive calls. The BFS branch leads directly to level-order traversal, which uses a queue to process all nodes at each depth before going deeper. Use this as a mental shortcut: if a problem involves parent-child order or sorted output, follow the DFS branch; if it involves levels or shortest distance, follow the BFS branch.
๐ข Tracing the Four Traversals on One Tree
A
/ \
B C
/ \ / \
D E F G
| Method | Output sequence |
| Pre-order | A B D E C F G |
| In-order | D B E A F C G |
| Post-order | D E B F G C A |
| Level-order | A B C D E F G |
Notice that in-order on a Binary Search Tree produces a perfectly sorted sequence โ that is one of the most useful properties in practice.
๐ All Four Traversals on One Labelled Tree
flowchart TD
R[4] --> L[2]
R --> Ri[6]
L --> LL[1]
L --> LR[3]
Ri --> RL[5]
Ri --> RR[7]
NoteA["Inorder: 1-2-3-4-5-6-7"]
NoteB["Preorder: 4-2-1-3-6-5-7"]
NoteC["Postorder: 1-3-2-5-7-6-4"]
This BST diagram shows a complete binary search tree with values 1โ7 and embeds all three DFS traversal output sequences directly as node labels. Inorder produces 1-2-3-4-5-6-7 (sorted ascending), Preorder produces 4-2-1-3-6-5-7 (root recorded first), and Postorder produces 1-3-2-5-7-6-4 (root recorded last). Notice that only Inorder gives a sorted result โ the other two reflect the tree's structural order, not numerical order. Trace each sequence manually by following the left-then-root-then-right rule for Inorder to confirm why this particular tree produces a perfectly sorted output.
โ๏ธ Recursive Templates for DFS Traversals
Every DFS traversal follows the same skeleton โ just change where you record the node:
class Node:
def __init__(self, val):
self.val = val
self.left = self.right = None
def preorder(node, out):
if node is None:
return
out.append(node.val) # โ record BEFORE children
preorder(node.left, out)
preorder(node.right, out)
def inorder(node, out):
if node is None:
return
inorder(node.left, out)
out.append(node.val) # โ record BETWEEN children
inorder(node.right, out)
def postorder(node, out):
if node is None:
return
postorder(node.left, out)
postorder(node.right, out)
out.append(node.val) # โ record AFTER children
The base case if node is None: return handles all edge cases automatically.
๐ Inorder Traversal: Drilling Left Before Visiting
flowchart TD
A[Start] --> B[Go Left]
B --> C{Left Null?}
C -- No --> B
C -- Yes --> D[Visit Node]
D --> E[Go Right]
E --> F{Right Null?}
F -- No --> G[Go Left Again]
G --> C
F -- Yes --> H[Backtrack]
This flowchart captures the recursive logic of in-order traversal as an explicit decision loop. Starting from any node, execution drills left until it hits a null (the base case), visits the current node, then turns right and repeats the entire left-drilling descent. The "Go Left Again" path after "Go Right" illustrates that the right subtree triggers its own full recursive descent โ not just a single step right. The key insight is that "Visit Node" always happens between the two recursive calls, which is precisely why in-order traversal on a BST produces a sorted sequence.
๐ง Deep Dive: How the Call Stack Powers Recursive DFS
Every recursive DFS call pushes a stack frame โ containing the current node pointer and the return address โ onto the call stack. When the base case is hit (node is None), frames pop in reverse. For a balanced tree of height log n, at most log n frames live on the stack simultaneously. For a skewed tree (each node has one child), all n frames stack up, risking stack overflow on large inputs.
| Tree shape | Stack depth | Risk |
| Balanced (height log n) | O(log n) | Safe for typical inputs |
| Skewed (height n) | O(n) | Stack overflow risk at ~10kโ100k nodes |
๐ Level-Order (BFS) with a Queue
DFS uses the call stack (or an explicit stack) for memory. Level-order uses a queue โ first-in, first-out:
from collections import deque
def level_order(root):
if root is None:
return []
queue = deque([root])
result = []
while queue:
node = queue.popleft()
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
To process one level at a time (common in interview problems), snapshot the queue size at the start of each iteration:
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
result.append(level)
๐ Real-World Applications: Which Problems Need Which Traversal
flowchart TD
A[Tree Problem] --> B{Need sorted output from BST?}
B -->|Yes| C[In-order]
B -->|No| D{Need to process children before parent?}
D -->|Yes| E[Post-order - delete, evaluate expression]
D -->|No| F{Need shortest path or level grouping?}
F -->|Yes| G[Level-order BFS]
F -->|No| H[Pre-order - serialize, copy, prefix traversal]
Common interview patterns:
| Problem | Best traversal | Why |
| Validate BST | In-order | Expects monotonically increasing sequence |
| Serialize/deserialize tree | Pre-order + null markers | Captures structure unambiguously |
| Calculate tree height | Post-order | Need children heights before parent |
| Find minimum depth | Level-order | BFS finds shallowest leaf first |
| Invert a binary tree | Pre-order or post-order | Either works; process each node once |
โ๏ธ Complexity and Stack Safety
For a tree with n nodes and height h:
| Traversal | Time | Space (recursive) | Space (iterative) |
| Pre / In / Post | O(n) | O(h) stack frames | O(h) explicit stack |
| Level-order | O(n) | N/A | O(w) queue, w = max width |
Skewed trees are dangerous for recursion. A linked-list-shaped tree (each node has only one child) makes h = n, causing O(n) stack depth. For production trees of unknown shape, prefer iterative DFS or convert to an explicit stack.
โ๏ธ Trade-offs & Failure Modes: Practical Pitfalls to Avoid
| Pitfall | Symptom | Fix |
| Forgetting the null base case | NullPointerException or infinite loop | Always check if node is None first |
| Using recursion on very deep trees | Stack overflow | Switch to iterative with explicit stack |
| Re-running full traversal inside a loop | O(nยฒ) behavior | Cache the result or restructure the loop |
| Confusing in-order with level-order | Wrong output order on non-BST | Draw a small example tree and trace manually |
๐งญ Decision Guide: Choosing the Right Traversal
- Sorted output or BST validation? โ In-order. It visits Left โ Root โ Right, yielding ascending key order from any BST.
- Serialize, copy, or reconstruct a tree? โ Pre-order. Root-first capture records structure unambiguously with null markers.
- Compute values that need both subtrees first (height, diameter, subtree sums)? โ Post-order. Children bubble results up before the parent decides.
- Shortest path, level grouping, or minimum depth? โ Level-order (BFS). It naturally visits shallow nodes before deep ones.
๐ฏ What to Learn Next
๐งช Practice: Trace Before You Code
The single most effective habit when learning traversals is to trace by hand before writing any code. Take this five-node tree:
1
/ \
2 3
/ \
4 5
Walk through each traversal step by step:
- Pre-order (Root โ Left โ Right): Visit 1, descend left to 2, descend left to 4 (leaf, return), go right to 5 (leaf, return), return to 1, go right to 3 (leaf).
- In-order (Left โ Root โ Right): Drill all the way left to 4 first, back up to 2, then 5, back up to 1, then 3.
- Post-order (Left โ Right โ Root): Visit 4, then 5, then 2 (both children done), then 3, then 1 last.
- Level-order: Row by row โ 1, then 2 and 3 together, then 4 and 5 together.
| Traversal | Output sequence |
| Pre-order | 1 โ 2 โ 4 โ 5 โ 3 |
| In-order | 4 โ 2 โ 5 โ 1 โ 3 |
| Post-order | 4 โ 5 โ 2 โ 3 โ 1 |
| Level-order | 1 โ 2 โ 3 โ 4 โ 5 |
Try tracing the same tree after removing node 5 (making it unbalanced) and observe how each sequence shifts. This exercise ingrains the mental model faster than reading any amount of code.
๐ ๏ธ Java Standard Library: Iterative DFS and BFS Without Recursion
The Java standard library (java.util) ships ArrayDeque, LinkedList, and all the primitives needed to implement every traversal discussed in this post without a single recursive call โ crucial for avoiding StackOverflowError on deep or skewed trees in production.
The library solves the stack-safety problem described in the deep-dive section: by using an explicit Deque as the stack, the tree depth only bounds heap memory (growable), not the JVM call stack (fixed, typically 512 KBโ8 MB).
import java.util.*;
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) { this.val = val; }
}
public class IterativeTraversals {
// --- Iterative Pre-order: Root โ Left โ Right ---
public List<Integer> preorder(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val); // record BEFORE children
if (node.right != null) stack.push(node.right); // right first (LIFO)
if (node.left != null) stack.push(node.left);
}
return result;
}
// --- Iterative In-order: Left โ Root โ Right ---
public List<Integer> inorder(TreeNode root) {
List<Integer> result = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) { // drill all the way left
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
result.add(curr.val); // record BETWEEN children
curr = curr.right; // then explore right
}
return result;
}
// --- Level-order BFS using a Queue ---
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size(); // snapshot: nodes at this level
List<Integer> level = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(level);
}
return result;
}
// --- Quick smoke test ---
public static void main(String[] args) {
// 1
// / \
// 2 3
// / \
// 4 5
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2); root.right = new TreeNode(3);
root.left.left = new TreeNode(4); root.left.right = new TreeNode(5);
IterativeTraversals t = new IterativeTraversals();
System.out.println("Pre-order: " + t.preorder(root)); // [1, 2, 4, 5, 3]
System.out.println("In-order: " + t.inorder(root)); // [4, 2, 5, 1, 3]
System.out.println("Level-order: " + t.levelOrder(root)); // [[1], [2,3], [4,5]]
}
}
No recursion โ no risk of StackOverflowError, no matter how deep or skewed the input tree. This is the production-safe template for any tree traversal in Java.
For a full deep-dive on Java's java.util collections for algorithm problems, a dedicated follow-up post is planned.
๐ Patterns That Repeat
Once you can trace the four traversals confidently, you will recognise the same structural patterns across many interview problems:
"Process children before the parent" โ whenever a problem needs information about both subtrees before deciding at the current node (height, diameter, subtree sums), post-order is the natural fit. The result at each node bubbles up from the leaves.
"Visit nodes in sorted order" โ on a Binary Search Tree, in-order traversal delivers a free sorted sequence. Problems like validating a BST, finding the k-th smallest element, or converting a BST to a sorted array all exploit this property directly.
"Level-by-level grouping" โ interview problems asking for zigzag level output, the right-side view of a tree, or minimum depth are all variations of level-order BFS with a small twist on how each level's nodes are collected or filtered.
"Serialize and reconstruct" โ pre-order traversal with explicit null markers produces a string that can be decoded back into the exact same tree, because the root always appears first and the structure is unambiguous. This pattern underpins many tree encoding and cloning problems.
Recognising which pattern a new problem fits usually narrows you to a single traversal choice before you write a single line of code.
๐ TLDR: Summary & Key Takeaways
- The four traversals (pre-order, in-order, post-order, level-order) are the same tree visited in different orders โ match the order to your problem.
- In-order on a BST always produces sorted output.
- Post-order is the safe choice when processing children before parents (deletion, expression evaluation).
- Level-order (BFS) is the right tool for shortest-path or depth-sensitive problems.
- Prefer iterative DFS for trees of unknown depth to avoid stack overflow.
๐ Practice Quiz
Which traversal produces sorted output when applied to a Binary Search Tree?
- A) Pre-order
- B) In-order
- C) Level-order
- D) Post-order
Correct Answer: B โ In-order traversal visits Left โ Root โ Right, which on a BST yields node values in ascending sorted order because every left subtree holds smaller keys and every right subtree holds larger keys.
Why is post-order the correct choice for deleting a tree?
- A) Post-order is always faster than other traversals
- B) Post-order processes children before the parent, so no parent is deleted while children still exist
- C) Post-order avoids recursion entirely
- D) Post-order uses less memory than pre-order
Correct Answer: B โ By visiting both children before the parent, post-order guarantees that every child node is freed or deallocated before its parent, preventing dangling references and memory leaks.
What data structure does a BFS level-order traversal use?
- A) Stack
- B) Queue
- C) Priority queue
- D) Hash map
Correct Answer: B โ A queue (FIFO) ensures nodes are processed in the exact order they are discovered, which naturally produces the level-by-level output sequence that characterises BFS.
๐ Related Posts

Written by
Abstract Algorithms
@abstractalgorithms
More Posts

Adapting to Virtual Threads for Spring Developers
TLDR: Platform threads (one OS thread per request) max out at a few hundred concurrent I/O-bound requests. Virtual threads (JDK 21+) allow millions โ with zero I/O-blocking cost. Spring Boot 3.2 enables them with a single property. Avoid synchronized...

Java 8 to Java 25: How Java Evolved from Boilerplate to a Modern Language
TLDR: Java went from the most verbose mainstream language to one of the most expressive. Lambdas killed anonymous inner classes. Records killed POJOs. Virtual threads killed thread pools for I/O work.
Data Anomalies in Distributed Systems: Split Brain, Clock Skew, Stale Reads, and More
TLDR: Distributed systems produce anomalies not because the code is buggy โ but because physics makes it impossible to be perfectly consistent, available, and partition-tolerant simultaneously. Split brain, stale reads, clock skew, causality violatio...
Sharding Approaches in SQL and NoSQL: Range, Hash, and Directory-Based Strategies Compared
TLDR: Sharding splits your database across multiple physical nodes so no single machine carries all the data or absorbs all the writes. The strategy you choose โ range, hash, consistent hashing, or directory โ determines whether range queries stay ch...
