Chapter 10
20 min read
Section 60 of 261

Preorder, Inorder, Postorder Traversals

Tree Fundamentals

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:

🐍python
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:

NameStatement orderMnemonic
Preordervisit, walk-left, walk-rightRoot FIRST, then children
Inorderwalk-left, visit, walk-rightRoot in the MIDDLE
Postorderwalk-left, walk-right, visitRoot 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

We separate the act of visiting from the act of recursing because visiting is the only place where work happens. Different applications make “visit” mean different things: appending to a list, printing, computing a hash, freeing memory, writing a byte. The traversal is the scaffolding; the visit is the business logic.

The Recurrence and Its Mathematics

Let TT be a binary tree with root rr, left subtree LL, and right subtree RR. Let Pre(T)\text{Pre}(T), In(T)\text{In}(T), and Post(T)\text{Post}(T) denote the sequences produced by the three traversals. Each obeys a clean recurrence:

  • Pre(T)=[r]Pre(L)Pre(R)\text{Pre}(T) = [r] \cdot \text{Pre}(L) \cdot \text{Pre}(R) — root, then preorder of left, then preorder of right.
  • In(T)=In(L)[r]In(R)\text{In}(T) = \text{In}(L) \cdot [r] \cdot \text{In}(R) — inorder of left, root, inorder of right.
  • Post(T)=Post(L)Post(R)[r]\text{Post}(T) = \text{Post}(L) \cdot \text{Post}(R) \cdot [r] — postorder of left, postorder of right, root.
  • Base case for all three: Pre()=In()=Post()=[]\text{Pre}(\emptyset) = \text{In}(\emptyset) = \text{Post}(\emptyset) = [\,].

From these recurrences we can already prove three properties without writing any code. Let n(T)n(T) be the number of nodes and h(T)h(T) the height (longest root-to-leaf path).

  • Length. Pre(T)=In(T)=Post(T)=n(T)|\text{Pre}(T)| = |\text{In}(T)| = |\text{Post}(T)| = n(T). By induction on the recurrence: each contributes 1+n(L)+n(R)=n(T)1 + n(L) + n(R) = n(T). Every node appears exactly once, no matter the order.
  • Time. The recurrence T(n)=T(nL)+T(nR)+O(1)T(n) = T(n_L) + T(n_R) + O(1) with nL+nR=n1n_L + n_R = n - 1 solves to T(n)=Θ(n)T(n) = \Theta(n). Linear in the size of the tree, regardless of shape.
  • Space. Recursion depth equals the height: S(n)=O(h(T))S(n) = O(h(T)). For a balanced tree that is O(logn)O(\log n); for a degenerate path-like tree it is O(n)O(n). This is why pathological trees are dangerous on platforms with fixed-size stacks.

A surprising consequence

Inorder of any tree concatenates the inorder of its left subtree, the root, and the inorder of its right subtree. If the tree is a binary search tree — every left descendant smaller than the root and every right descendant larger — then by induction the inorder traversal returns the keys in sorted order. That is exactly why BSTs are useful. Inorder is not just a traversal rule; it is the reason a BST works as an ordered data structure.

Worked Example: One Tree, Three Walks

Consider the seven-node tree:

📝text
11
2           / \
3          2   3
4         / \ / \
5        4  5 6  7

Apply each recurrence by hand. The bracketed comments show which substitution we just made.

OrderStep-by-step expansionFinal sequence
Preorder[1] · Pre(L₁) · Pre(R₁) → [1] · [2,4,5] · [3,6,7]1, 2, 4, 5, 3, 6, 7
InorderIn(L₁) · [1] · In(R₁) → [4,2,5] · [1] · [6,3,7]4, 2, 5, 1, 6, 3, 7
PostorderPost(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 rr). Everything after it is the preorder of one of the children.
  • Postorder ends with the root (the last element is always rr). 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.

step 1 / 23
1234567
call stack (top → bottom)
(empty)
depth = 0
action > start: output=[ ], call stack=[ ]
output = []

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

Notice that the call stack's maximum depth is always the height of the tree (3 for this balanced example) — independent of which traversal you pick. The three orders differ only in when the amber “visit” flash happens relative to the blue “descend” flashes. The shape of the descent is identical.

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.

📄c
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 == NULL is 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 *root documents read-only intent. Traversals never mutate structure; the const qualifier lets the compiler enforce that promise and lets callers pass in a const Node * without a cast.
  • Freeing the tree is itself a postorder traversal. The reason: you must free the children before the parent, otherwise parent->left would 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

Each recursive call adds a frame to the C runtime stack — typically 1–8 MB on Linux/macOS by default. At roughly 50–100 bytes per frame, you can recurse about 10410^4 to 10510^5 levels deep before SIGSEGV. A balanced million-node tree is fine (height ≈ 20). A skewed million-node linked-list-shaped tree is not. For untrusted input, prefer the iterative versions later in this section.

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.

Preorder / Inorder / Postorder — recursive Python
🐍python
2Why __slots__?

By default, every Python object carries a per-instance __dict__ for attribute storage — useful but heavy. __slots__ tells the runtime: this class only ever has these three attributes, allocate exactly three slots and skip the dict. For a tree of millions of nodes that single line saves about 56 bytes per node and improves cache locality during traversal.

3Constructor with optional children

Default left=None and right=None lets us build a leaf with Node(4) and an internal node with Node(2, Node(4), Node(5)). The same code path constructs every node — a leaf is just an internal node whose children happen to be None.

9preorder signature

We pass `out` (a list) instead of returning a fresh list each call. Returning would force every recursive call to allocate, then concatenate — turning O(n) into O(n²) work because list concatenation copies. The accumulator pattern avoids that trap.

EXAMPLE
preorder(root, out=[])  # caller passes the same list to every recursive call
10The base case is the whole correctness story

An empty subtree has nothing to visit. Returning immediately on None is what makes the recursion terminate — every leaf has two None children, so each leaf triggers exactly two terminating calls. Forgetting this line is the #1 reason tree recursions blow the stack.

LOOP TRACE · 2 iterations
preorder(None, out)
root = None
action = return immediately
preorder(Node(4), out)
root = Node(4)
action = fall through
11Visit root FIRST — the defining moment

The order in which this line appears among the three statements is what makes this preorder. Move it after the two recursive calls and you have postorder. Move it between them and you have inorder. The recursion shape is identical; only the line order changes. That is a profound realization: the three traversals are the three permutations of {visit, recurse-left, recurse-right} that put visit somewhere among the recursive calls.

EXAMPLE
out.append(root.value)  # root → output BEFORE descending
12Recurse into the left subtree

We call preorder again with root.left. Whatever subtree hangs there is processed in full — all its nodes are visited in preorder — before we move on to root.right on the next line. This is why traversals are depth-first: we go all the way down the left spine before touching anything to the right.

13Recurse into the right subtree

By the time this line executes, the left subtree has been completely visited. The recursion is using the call stack to remember 'when I finish on the left, come back here and start the right'. That is exactly what an explicit stack would store.

17Inorder — the same shape, one line moved

Compare inorder to preorder: literally only the position of out.append is different. This is the recursion-shape symmetry — same control flow, same stack depth, same O(n) work. The output, though, is dramatically different — inorder of a BST yields a sorted list.

19Visit between the two recursions

By the time we get here, the entire left subtree has been appended to `out`. The current root therefore sits at exactly the position in the output that has all left descendants before it and all right descendants after it. For a BST that property is the magic that produces a sorted list.

LOOP TRACE · 3 iterations
inorder(Node(2)) — before line 19
out = [4] # left subtree of 2 done
inorder(Node(2)) — at line 19
out = [4, 2]
inorder(Node(2)) — after recursing right
out = [4, 2, 5]
25Postorder — visit LAST

Postorder visits children before the parent. The crucial property: when out.append(root.value) runs, every descendant of root has already been appended. That is why postorder is the order to use whenever the parent's work depends on the children's results — destroying a tree, computing subtree sums, evaluating expression trees, releasing locks held in nested scope.

EXAMPLE
freeing memory:  free(left); free(right); free(self)  ← postorder
31Construct the same 7-node tree as the C example

Notice how the constructor mirrors the recursive shape of the tree itself: Node(2, Node(4), Node(5)) looks like the subtree it builds. This is one of the rare cases where Python source code physically resembles the data structure it produces.

34Calling all three traversals on the same tree

Three calls, three different orderings of the SAME seven values. The tree is unchanged between calls. That is the reader's mental model for the rest of this section: a tree has many useful linearizations, and the traversal order is what selects which one you want.

LOOP TRACE · 3 iterations
preorder
out = [1, 2, 4, 5, 3, 6, 7]
inorder
out = [4, 2, 5, 1, 6, 3, 7]
postorder
out = [4, 5, 2, 6, 7, 3, 1]
26 lines without explanation
1# A textbook binary tree node.
2class Node:
3    __slots__ = ("value", "left", "right")
4    def __init__(self, value, left=None, right=None):
5        self.value = value
6        self.left  = left
7        self.right = right
8
9def preorder(root, out):
10    if root is None:                # base case
11        return
12    out.append(root.value)          # VISIT root
13    preorder(root.left,  out)       # then left
14    preorder(root.right, out)       # then right
15
16def inorder(root, out):
17    if root is None:
18        return
19    inorder(root.left,  out)        # left
20    out.append(root.value)          # VISIT root in the middle
21    inorder(root.right, out)        # right
22
23def postorder(root, out):
24    if root is None:
25        return
26    postorder(root.left,  out)      # left
27    postorder(root.right, out)      # right
28    out.append(root.value)          # VISIT root last
29
30# Build the same 7-node tree
31r = Node(1,
32        Node(2, Node(4), Node(5)),
33        Node(3, Node(6), Node(7)))
34
35pre, ino, post = [], [], []
36preorder(r, pre);   print(pre)    # [1, 2, 4, 5, 3, 6, 7]
37inorder(r, ino);    print(ino)    # [4, 2, 5, 1, 6, 3, 7]
38postorder(r, post); print(post)   # [4, 5, 2, 6, 7, 3, 1]

C vs Python — same algorithm, different cost story

Both versions are O(n)O(n) time and O(h)O(h) auxiliary stack space. The constants differ: C does a couple of pointer derefs and a printf per node; Python does an attribute lookup, a list append (amortized O(1)), and pays interpreter overhead. For a million-node tree that is roughly 30 ms in C versus 300 ms in CPython — same shape, ten times the constant.

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.

Iterative inorder, preorder, and postorder using an explicit stack
🐍python
5Two pieces of state replace the call stack

The recursive version had ONE piece of state — the call stack. Here we expose two: an explicit stack of pending nodes, and a pointer `cur` that tells us which subtree we are currently descending into. Together they encode exactly the information a recursive frame would have stored.

7The outer loop's exit condition

We continue while EITHER we still have a current node to descend into OR the stack has remembered ancestors waiting for their visit. Both being false means there is nothing left to do — the tree is fully traversed.

9Inner loop: race down the left spine

Push `cur` onto the stack, then move to its left child. We keep doing this until cur is None — at which point we have stored every left-ancestor of the current position. This is the iterative encoding of 'recurse into left'.

LOOP TRACE · 4 iterations
starting at Node(1)
cur = 1
stack = []
after iteration 1
cur = 2
stack = [1]
after iteration 2
cur = 4
stack = [1, 2]
after iteration 3
cur = None
stack = [1, 2, 4]
13Pop and visit

Once we cannot go further left, the top of the stack is the next node whose 'middle' moment has arrived — its left subtree is fully done, but we have not yet visited it or its right subtree. Pop it, append its value. This append is the inorder 'visit'.

15Pivot to the right subtree

Setting cur = popped.right starts the same descent again, but now from the right child. The outer while loop then continues, re-running the inner loop on this new starting point. The combination of 'descend left, pop and visit, pivot right' is the entire inorder traversal in one tight package.

22Iterative preorder is even simpler

We seed the stack with root. Each iteration: pop, visit, push right then left. Pushing right first ensures left is popped FIRST on the next iteration — preserving the root-left-right order.

27Push right BEFORE left — the famous gotcha

If you push left first, you process right next (because the stack returns the most recently pushed). That would give you root-right-left order, the mirror of preorder. The order in which you push children to a stack is INVERTED in the order you process them. Memorize that — it is the source of half of all stack-based traversal bugs.

LOOP TRACE · 3 iterations
after popping 1, push 3 then 2
stack = [3, 2] # 2 is on top → popped next
pop 2, push 5 then 4
stack = [3, 5, 4]
pop 4 (leaf, push nothing)
stack = [3, 5]
35Postorder via two stacks — a beautiful trick

Postorder is the awkward one for stacks because the parent must wait for both children. The shortcut: do a modified preorder that pushes LEFT first, then RIGHT — this gives you root-right-left order. Reverse it and you get left-right-root, which is exactly postorder. Two stacks, one reversal, no special bookkeeping.

EXAMPLE
modified preorder: 1 3 7 6 2 5 4   →  reversed:  4 5 2 6 7 3 1  ✓
41Reverse-collect into the answer

s2 contains nodes in reverse postorder. We iterate s2 backward to extract values in proper postorder. Total work is still O(n) — every node is pushed onto each stack exactly once, plus one linear reversal.

36 lines without explanation
1# Iterative inorder using an explicit stack.
2# This is exactly what the recursive version is doing under the hood —
3# we are just managing the stack ourselves instead of letting the runtime do it.
4
5def inorder_iter(root):
6    out, stack = [], []
7    cur = root
8    while cur is not None or stack:
9        # 1. walk all the way left, pushing every node we pass.
10        while cur is not None:
11            stack.append(cur)
12            cur = cur.left
13        # 2. no more left to descend → pop and visit.
14        cur = stack.pop()
15        out.append(cur.value)
16        # 3. now turn right and repeat.
17        cur = cur.right
18    return out
19
20# Iterative preorder: the cleanest of the three.
21def preorder_iter(root):
22    if root is None:
23        return []
24    out, stack = [], [root]
25    while stack:
26        node = stack.pop()
27        out.append(node.value)            # visit immediately
28        # Push RIGHT first so LEFT pops next — preorder = root, left, right.
29        if node.right is not None:
30            stack.append(node.right)
31        if node.left  is not None:
32            stack.append(node.left)
33    return out
34
35# Iterative postorder using two stacks (the easy way).
36def postorder_iter(root):
37    if root is None:
38        return []
39    s1, s2 = [root], []
40    while s1:
41        node = s1.pop()
42        s2.append(node)                   # collect in reverse-postorder
43        if node.left  is not None: s1.append(node.left)
44        if node.right is not None: s1.append(node.right)
45    return [n.value for n in reversed(s2)]

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: Θ(n)\Theta(n) time and O(h)O(h) auxiliary space.


Time, Space, and Correctness

We summarized the complexity already; here is the more careful breakdown side by side.

AspectRecursiveIterative (explicit stack)
TimeΘ(n)\Theta(n) — every node entered + left + rightΘ(n)\Theta(n) — every node pushed and popped exactly once
Auxiliary spaceO(h)O(h) on the call stackO(h)O(h) on the explicit stack
Worst-case spaceO(n)O(n) on a degenerate (path-like) treeO(n)O(n) on the same shape
Risk on deep inputstack overflow / SIGSEGVOOM 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 O(1)O(1) — a comparison, an append, perhaps a print. Sum over all nn nodes and you get Θ(n)\Theta(n). 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 Pre,In,Post\text{Pre}, \text{In}, \text{Post} are correct on the two subtrees LL and RR; the recurrence then constructs the correct sequence for TT 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 [a,b][a, b] by descending to aa, then inorder-iterating until you exceed bb.
  • 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 O(1)O(1) per step with no stack.

Postorder — “children before parent”

  • Memory deallocation (free_tree in 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 1+2×31 + 2 \times 3. 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

Ask yourself: does the parent's work depend on the children's answers? If yes → postorder. Does the parent emit a context that controls how children are processed? If yes → preorder. Are you producing a sorted output from an ordered tree? Inorder. Most production bugs in tree algorithms come from picking the wrong one and patching around it instead of switching.

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:

🐍python
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 T(n)=2T(n/2)+O(n)T(n) = 2T(n/2) + O(n), giving O(nlogn)O(n \log n); on a path-shaped tree it is O(n2)O(n^2). 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 tonn. 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.

Loading comments...