The Question Traversal Answers
A tree is a fundamentally two-dimensional object — children branch sideways from each parent. But almost every operation we want to perform on it (print it, save it to disk, compute a checksum, hand it to another function) demands a one-dimensional answer: a list, a string, a stream of bytes. The bridge between those two worlds is a traversal: a deterministic rule for visiting every node exactly once and turning the tree into a sequence.
For binary trees, three traversal rules dominate every textbook, every parser, every database engine, every git internals manual: preorder, inorder, and postorder. They are not arbitrary. They correspond to the three positions where you can place “visit the root” among “walk the left subtree” and “walk the right subtree” — and each position turns out to encode a different real-world meaning.
The single insight of this section: a tree has many useful linearizations. Preorder, inorder, and postorder are the three you can build by recursive descent, and which one you pick is a statement about when the parent's work happens relative to its children.
Three Orders, One Recursion
Every binary-tree traversal we will study has the same three-line skeleton:
1def walk(node):
2 if node is None: return
3 # ... three things to do, in some order:
4 # visit(node)
5 # walk(node.left)
6 # walk(node.right)The three orderings of those three statements with visit in different positions give us the three names:
| Name | Statement order | Mnemonic |
|---|---|---|
| Preorder | visit, walk-left, walk-right | Root FIRST, then children |
| Inorder | walk-left, visit, walk-right | Root in the MIDDLE |
| Postorder | walk-left, walk-right, visit | Root LAST, after children |
The other three permutations (right-first variants like visit, right, left) exist and are called reverse preorder/inorder/postorder. They produce the mirror of the originals on a flipped tree, and we rarely need them as primitives. The three above are the canonical set.
Why “visit” is its own line
The Recurrence and Its Mathematics
Let be a binary tree with root , left subtree , and right subtree . Let , , and denote the sequences produced by the three traversals. Each obeys a clean recurrence:
- — root, then preorder of left, then preorder of right.
- — inorder of left, root, inorder of right.
- — postorder of left, postorder of right, root.
- Base case for all three: .
From these recurrences we can already prove three properties without writing any code. Let be the number of nodes and the height (longest root-to-leaf path).
- Length. . By induction on the recurrence: each contributes . Every node appears exactly once, no matter the order.
- Time. The recurrence with solves to . Linear in the size of the tree, regardless of shape.
- Space. Recursion depth equals the height: . For a balanced tree that is ; for a degenerate path-like tree it is . This is why pathological trees are dangerous on platforms with fixed-size stacks.
A surprising consequence
Worked Example: One Tree, Three Walks
Consider the seven-node tree:
11
2 / \
3 2 3
4 / \ / \
5 4 5 6 7Apply each recurrence by hand. The bracketed comments show which substitution we just made.
| Order | Step-by-step expansion | Final sequence |
|---|---|---|
| Preorder | [1] · Pre(L₁) · Pre(R₁) → [1] · [2,4,5] · [3,6,7] | 1, 2, 4, 5, 3, 6, 7 |
| Inorder | In(L₁) · [1] · In(R₁) → [4,2,5] · [1] · [6,3,7] | 4, 2, 5, 1, 6, 3, 7 |
| Postorder | Post(L₁) · Post(R₁) · [1] → [4,5,2] · [6,7,3] · [1] | 4, 5, 2, 6, 7, 3, 1 |
Three properties to notice in the sequences themselves, before we touch any code:
- Preorder starts with the root (the first element is always ). Everything after it is the preorder of one of the children.
- Postorder ends with the root (the last element is always ). Read postorder backwards and you are reading reverse-preorder of the mirror tree.
- Inorder splits at the root. If you know the root, everything to its left in the inorder array is the inorder of the left subtree, and everything to the right is the inorder of the right subtree. This is the fact that lets us reconstruct a tree from preorder + inorder uniquely — a classic interview problem.
Interactive: Step-by-Step Traversal
Theory is cheap; intuition is expensive. The visualizer below runs each traversal one frame at a time on the same seven-node tree, animating the recursion stack on the right exactly as it would appear in a debugger. Switch the order, step through, replay — the goal is to see why the same shape of recursion produces three different sequences.
Pick an order, then step (or play). Blue = a frame just pushed onto the call stack. Amber = the node is being “visited” — its value is being appended to the output. Green = already in the output. Slate = the recursive call just returned. The right panel is the actual recursion stack — the runtime stack you would see in a debugger.
What to look for
Implementation in C (Recursive)
C is the right starting language because it makes the cost model painfully explicit: every node is an explicit pointer, every recursive call is an explicit stack frame, and the only way to release memory is to walk the tree once more.
1// tree.h ------------------------------------------------------------
2typedef struct Node {
3 int value;
4 struct Node *left;
5 struct Node *right;
6} Node;
7
8// Allocate a leaf with a given value.
9Node *make_node(int v) {
10 Node *n = (Node *) malloc(sizeof(Node));
11 if (n == NULL) {
12 fprintf(stderr, "out of memory\n");
13 exit(1);
14 }
15 n->value = v;
16 n->left = NULL;
17 n->right = NULL;
18 return n;
19}
20
21// traverse.c --------------------------------------------------------
22void preorder(const Node *root) {
23 if (root == NULL) return; // base case: empty subtree
24 printf("%d ", root->value); // VISIT (root first)
25 preorder(root->left); // then left subtree
26 preorder(root->right); // then right subtree
27}
28
29void inorder(const Node *root) {
30 if (root == NULL) return;
31 inorder(root->left); // left
32 printf("%d ", root->value); // VISIT (root in the middle)
33 inorder(root->right); // right
34}
35
36void postorder(const Node *root) {
37 if (root == NULL) return;
38 postorder(root->left); // left
39 postorder(root->right); // right
40 printf("%d ", root->value); // VISIT (root last)
41}
42
43// Driver
44int main(void) {
45 // Build: 1
46 // / \
47 // 2 3
48 // / \ / \
49 // 4 5 6 7
50 Node *r = make_node(1);
51 r->left = make_node(2);
52 r->right = make_node(3);
53 r->left->left = make_node(4);
54 r->left->right = make_node(5);
55 r->right->left = make_node(6);
56 r->right->right = make_node(7);
57
58 preorder(r); printf("\n"); // 1 2 4 5 3 6 7
59 inorder(r); printf("\n"); // 4 2 5 1 6 3 7
60 postorder(r); printf("\n"); // 4 5 2 6 7 3 1
61
62 free_tree(r); // postorder freeing — see below
63 return 0;
64}Three observations that are easy to miss but matter for production code:
- The base case
root == NULLis not optional. Without it, the recursion would dereference a null pointer at the first leaf and crash. The base case is what makes the recursion close on itself. const Node *rootdocuments read-only intent. Traversals never mutate structure; theconstqualifier lets the compiler enforce that promise and lets callers pass in aconst Node *without a cast.- Freeing the tree is itself a postorder traversal. The reason: you must free the children before the parent, otherwise
parent->leftwould be a dangling pointer the moment you dereferenced it. We will revisit this in the applications section — it is the prototype example of why postorder exists.
Stack overflow on deep trees
The Same Algorithms in Python
The Python translation is line-for-line identical in structure. The differences are ergonomic — automatic memory management, list as the visit accumulator, no explicit pointers — but the algorithm is exactly the same. Read the explanations carefully: the line-position trick in the recursive body is the entire idea you take from this section.
C vs Python — same algorithm, different cost story
Iterative Traversal with an Explicit Stack
The recursive versions are elegant but they trust the runtime stack. When the tree might be very deep (parsed user input, file-system trees, malicious data, embedded systems with tiny stacks) we want to manage the stack ourselves. The good news: every recursion can be unrolled into an explicit stack, and tree traversal is the cleanest example of that transformation in the whole curriculum.
The deep takeaway: a recursive traversal and a stack-based iterative traversal are the same algorithm. The recursion uses the call stack the language gave it for free; the iterative version uses a list it manages by hand. They both push when descending, pop when returning, and visit nodes in the same order. The cost model is identical: time and auxiliary space.
Time, Space, and Correctness
We summarized the complexity already; here is the more careful breakdown side by side.
| Aspect | Recursive | Iterative (explicit stack) |
|---|---|---|
| Time | — every node entered + left + right | — every node pushed and popped exactly once |
| Auxiliary space | on the call stack | on the explicit stack |
| Worst-case space | on a degenerate (path-like) tree | on the same shape |
| Risk on deep input | stack overflow / SIGSEGV | OOM at worst — but you control the data structure |
| Code size | ≈ 4 lines per order | ≈ 8–12 lines per order |
Why the analysis is so clean. Each node is the focus of exactly one recursive call (or one push/pop on the iterative side). The work at the focus is — a comparison, an append, perhaps a print. Sum over all nodes and you get . There is no slack and there are no hidden quadratic costs as long as you avoid the return new_list = recurse(left) + [root] + recurse(right) pattern, which copies lists and turns linear into quadratic.
Why it is correct. Structural induction: assume are correct on the two subtrees and ; the recurrence then constructs the correct sequence for by definition. The base case (empty tree → empty list) is trivially correct. Induction does the rest.
Where Each Order Lives in Real Systems
The three traversals are not academic curiosities. Each one solves a class of problems where the timing of “visit the parent” relative to “visit the children” carries meaning.
Preorder — “parent first”
- File-system listings like
find,ls -R, and tree-style dumps print the directory before its contents. The output reads top-down, which is what users expect. - Tree serialization / cloning. When you serialize a tree to JSON or to disk, the natural order is preorder (root, then children) so the reader can construct the parent first and attach children as they arrive. Git's tree objects, S-expressions, and most AST dump formats all use preorder.
- Prefix expressions in Lisp:
(+ 1 (* 2 3))is the preorder of the expression tree. - DOM rendering in browsers: when the layout engine decides where each element goes, it measures parents before children — a preorder pass.
- Tree pretty-printers in compilers (clang
-ast-dump, rustc HIR dumps) emit nodes in preorder so an indented stream visually matches the tree shape.
Inorder — “parent in the middle”
- BST iteration. The single most important application: inorder of a binary search tree visits keys in sorted order. Database indices built on B-trees and B+ trees use the same idea (generalized to many-way nodes).
- Range queries. Once you can iterate inorder, you can stop at any predicate — give me all keys in by descending to , then inorder-iterating until you exceed .
- Infix-notation expression trees. If a node represents a binary operator and its children are the operands, inorder yields the human-readable infix form (after adding parentheses for precedence).
- Music sequencers and beat-tree models for rhythm: inorder traversal of a metric tree gives the order in which beats are played.
- Threaded binary trees exploit inorder so heavily that they thread otherwise-null child pointers to point at the inorder predecessor/successor — turning iteration into per step with no stack.
Postorder — “children before parent”
- Memory deallocation (
free_treein C, RAII destructors in C++). You must free the children before the parent — otherwise the parent's child pointers become stale before you have visited them. - Postfix (Reverse Polish) notation.
1 2 3 * +is the postorder of the expression tree for . HP calculators, Forth, PostScript, and the JVM bytecode evaluator all consume postfix because it requires only a stack — no operator-precedence parsing at evaluation time. - Dependency resolution. Build systems (
make, Bazel, npm) topologically sort tasks via postorder of the dependency DAG: a task is emitted only after every prerequisite is emitted. The same logic schedules cells in spreadsheets and resolves layout in CSS. - Subtree aggregations — sum, max, height, count, hash, AABB union, signed-distance merging in spatial data structures. The parent's aggregate requires the children's aggregates, so the parent runs last.
- Code generation in compilers. To emit code for an expression node, you first emit code that computes its children into registers — postorder on the AST.
- Lock release in nested scopes and transaction unwinding: locks are released in the reverse order they were acquired, which is exactly postorder of the scope tree.
Diagnostic test for which order you need
Pitfalls and Edge Cases
The empty tree
Every implementation must handle root is None gracefully. The recurrence dictates an empty output. Skipping the base case crashes; turning it into raise is overzealous — the empty traversal is well-defined and frequently useful (e.g. when a subtree is genuinely missing in a serialized format).
Returning new lists at every recursive call
The seductive but quadratic mistake:
1def preorder_bad(root):
2 if root is None: return []
3 return [root.value] + preorder_bad(root.left) + preorder_bad(root.right)Each + copies the entire accumulating list. On a balanced tree the work is , giving ; on a path-shaped tree it is . Always pass an accumulator (the “out” list pattern) or use yield for a generator. The complexity bug is invisible on toy inputs and lethal at scale.
Push order on iterative stacks
Pushing children onto the stack reverses the order in which they will be processed. For preorder you push right then left so left pops first. Get this backwards and you produce a mirror traversal — silently wrong, no exception.
Confusing inorder with sorted-order on non-BSTs
Inorder yields sorted output only when the tree satisfies the BST property. On a generic binary tree it just produces some permutation of the values. Beginners memorize “inorder is sorted” and apply it everywhere; the correct rule is “inorder of a BST is sorted”.
Stack overflow on adversarial input
A linked-list-shaped tree built from sorted inserts into an unbalanced BST can have height equal to. On Python, that triggers RecursionError at about 1000 levels by default. Defenses: sys.setrecursionlimit (cheap, papers over the symptom), switch to the iterative version (correct), or rebalance the tree (correct and faster long-term).
Modifying the tree while traversing it
If visit deletes nodes, splices in subtrees, or rotates ancestors, the recursion sees an inconsistent state. The classic safe pattern is “collect first, mutate later”: do a read-only traversal that produces a list of work items, then process the list in a second pass.
Quick Checks
Quick Check
Given the tree below, what does an inorder traversal output? 5 / \ 3 8 / \ \ 1 4 9
Quick Check
You are writing free_tree(root) in C. Which traversal order MUST you use, and why?
Quick Check
Which statement about the recursive and iterative versions of preorder is TRUE?
Quick Check
If preorder of a binary tree is [A, B, D, E, C, F, G] and inorder is [D, B, E, A, F, C, G], which node is the LEFT child of A?
The three orders feel like minor permutations, but they are the foundation on which BSTs, ASTs, build systems, garbage collectors, and database indices stand. The next section turns the camera 90° and looks at level-order traversal — visiting a tree breadth-first instead of depth-first — which uses a queue instead of a stack and unlocks an entirely different family of applications.