Tree Traversals
Imagine you must print every employee in a company org chart. But you get to choose the order. Managers before subordinates, subordinates before managers, or level-by-level like a directory sorted by rank. Each choice reveals different information. The first shows structure top-down. The second lets you summarize before reporting up. The third shows who is "close" to the top. This is exactly the problem tree traversal solves. It means visiting every node of a tree exactly once, in a systematic order that serves a specific purpose. Traversals are not an academic exercise. Compilers walk abstract syntax trees in postorder to evaluate expressions bottom-up. File systems walk directory trees in preorder to print paths before contents. Serialization formats like JSON and XML parsers build and read trees using these exact orders. Garbage collectors traverse object-reference graphs (often tree-like) to mark reachable memory. Every one of these systems relies on the same handful of traversal patterns studied here. The big picture is this: a tree has no single natural "reading order" the way an array does. So we must define one. The three classic depth-first orders — preorder, inorder, postorder — differ only in when a node is processed relative to its children. That is, before, between, or after visiting subtrees. Level-order traversal instead visits by depth, breadth-first. It uses a queue rather than the implicit or explicit stack of depth-first search. This article gives precise recursive and iterative pseudocode for all four traversals. It proves each runs in optimal time and or space. It traces a full example on a labeled tree. Finally, it connects these mechanics to the Binary Search Trees operations that depend on them.
- Four canonical traversals: preorder (root, left, right), inorder (left, root, right), postorder (left, right, root), level-order (breadth-first by depth).
- Each visits all nodes exactly once: all are time.
- Recursive versions mirror the tree's recursive definition; iterative versions use an explicit Stacks for DFS orders and a Queues and Deques for level-order.
- Inorder traversal of a Binary Search Trees yields keys in sorted order — the single most important practical fact about traversals.
- Space cost is for DFS traversals (call stack or explicit stack, = tree height) and for level-order (queue, = max width).
- Choice of traversal order encodes intent: preorder for copying/serializing, postorder for deletion/evaluation, inorder for sorted output, level-order for shortest-depth processing.
A tree traversal is a rule for ordering "visit this node" relative to "visit its children," and that single ordering choice determines what the walk is useful for.
The idea
A binary tree is either empty or a node with a key, a left subtree, and a right subtree (each itself a binary tree). A traversal is an algorithm that visits every node of the tree exactly once, producing a linear sequence from the tree's recursive structure. Because a node has three "parts" — itself, its left subtree, its right subtree — there are three natural depth-first orders depending on when the node itself is emitted relative to the two recursive subtree visits.
Preorder visits the node first, then recursively traverses the left subtree, then the right: root, left, right (often written N-L-R). Inorder visits left, then the node, then right (L-N-R). Postorder visits left, then right, then the node last (L-R-N). All three are depth-first: they plunge to a leaf before backtracking, and they can be implemented either with the language's own call stack (recursion) or an explicit stack that simulates it.
Level-order traversal (also called breadth-first traversal) abandons the root/left/right framing entirely. It visits nodes in order of increasing depth — all nodes at depth 0, then depth 1, then depth 2, and so on — using a Queues and Deques to remember which nodes to visit next, rather than a stack. Level-order has no natural recursive formulation; it is inherently iterative. Together these four orders form the complete basic toolkit: three ways to linearize a tree depth-first, one way breadth-first.
How it works
Recursive depth-first traversals. Each of the three DFS orders differs only in where the "visit(node)" line sits relative to the two recursive calls. ``` PREORDER(node): if node is null: return visit(node) PREORDER(node.left) PREORDER(node.right) INORDER(node): if node is null: return INORDER(node.left) visit(node) INORDER(node.right) POSTORDER(node): if node is null: return POSTORDER(node.left) POSTORDER(node.right) visit(node) ``` The base case — `node is null` — terminates recursion at every leaf's non-existent child. Every internal call makes exactly two recursive calls, one per child, so the recursion structure exactly mirrors the tree's own shape.
Iterative preorder simulates recursion with an explicit stack. Because preorder visits the node before its children, we can push the node, pop it, visit it immediately, then push right before left (so left is popped and processed first): ``` ITERATIVE-PREORDER(root): if root is null: return stack = new Stack() stack.push(root) while stack is not empty: node = stack.pop() visit(node) if node.right is not null: stack.push(node.right) if node.left is not null: stack.push(node.left) ```
Iterative inorder is trickier because a node is visited only after its entire left subtree is done. The standard technique walks left as far as possible, pushing every node along the way, then backtracks: ``` ITERATIVE-INORDER(root): stack = new Stack() node = root while node is not null or stack is not empty: while node is not null: // descend left, remembering the path stack.push(node) node = node.left node = stack.pop() // leftmost unvisited ancestor visit(node) node = node.right // now explore its right subtree ```
Iterative postorder is the hardest of the three because a node is visited last, after both children — so we must know we have already finished the right subtree before visiting. One clean method uses two stacks: ``` ITERATIVE-POSTORDER(root): if root is null: return stack1 = new Stack(); stack2 = new Stack() stack1.push(root) while stack1 is not empty: node = stack1.pop() stack2.push(node) if node.left is not null: stack1.push(node.left) if node.right is not null: stack1.push(node.right) while stack2 is not empty: visit(stack2.pop()) // stack2 unwinds in L-R-N order ``` This works because `stack1` produces a root, right, left order (mirror of preorder with children pushed in the opposite order); reversing that entire sequence — which is exactly what popping `stack2` does — yields left, right, root, i.e. postorder.
Level-order traversal uses a queue instead of a stack, since we want to process nodes in the order they were discovered (FIFO), not the reverse (LIFO): ``` LEVEL-ORDER(root): if root is null: return queue = new Queue() queue.enqueue(root) while queue is not empty: node = queue.dequeue() visit(node) if node.left is not null: queue.enqueue(node.left) if node.right is not null: queue.enqueue(node.right) ``` Each node enqueues its children right after being dequeued itself, so all depth- nodes are enqueued before any depth- node, guaranteeing breadth-first order.
Complexity & correctness
Time complexity — for all four traversals, where is the number of nodes. Every traversal visits each node exactly once and does work per node beyond the recursive/queueing overhead (one `visit` call, at most two pushes/enqueues, at most one pop/dequeue). Summing work over nodes gives total. This is asymptotically optimal: any algorithm that must output all node values needs at least time just to write the output. A short inductive argument makes the recursive correctness precise. Claim: `PREORDER(node)` visits every node in the subtree rooted at `node` exactly once, in the order root-then-left-subtree-then-right-subtree.
- Base case: if `node` is null, the subtree is empty and zero nodes are visited — vacuously correct.
- Inductive step: assume the claim holds for smaller subtrees (fewer nodes) — namely for `node.left` and `node.right`. The algorithm visits `node` itself once, then by the inductive hypothesis `PREORDER(node.left)` visits every node in the left subtree exactly once in preorder, then likewise for the right subtree. No node is shared between `{node}`, the left subtree, and the right subtree (they partition the node set of a tree by definition), so every node in `node`'s subtree is visited exactly once, in root/left/right order.
The identical argument, with the `visit` call relocated, proves inorder and postorder correct. This is structural induction on subtree size — the standard proof technique for any recursive tree algorithm. For level-order, correctness follows from a queue invariant: at the start of each iteration, the queue contains exactly the not-yet-visited nodes at depth followed by all discovered nodes at depth , for some that is non-decreasing over time. Since the queue is FIFO, all depth- entries are dequeued (and their depth- children enqueued) before any depth- node is dequeued. Hence nodes are visited in strictly non-decreasing depth order, which is the definition of level-order.
Space complexity.
| Traversal | Recursive | Iterative |
|---|---|---|
| Preorder / Inorder / Postorder | call-stack depth | explicit stack (postorder: two stacks, still ) |
| Level-order | — (no natural recursion) | queue, = max nodes at any single depth |
Here is the tree's height and its maximum width. In the worst case (a completely unbalanced, "linked-list-shaped" tree) , so DFS traversals cost space; in a balanced tree . Level-order's queue can hold up to nodes in the worst case too — for instance in a "bushy" tree where the bottom level alone has close to nodes. So in the worst case, all four traversals use auxiliary space, but recursive/stack-based DFS is far cheaper () on balanced trees, while level-order's cost tracks the tree's width rather than its height.
Tradeoffs. Recursive code is short and obviously correct by the induction above, but risks stack overflow on very deep, unbalanced trees (a real concern for adversarially built or degenerate trees) and carries function-call overhead. Iterative versions avoid call-stack limits and overhead but are more error-prone to write correctly — especially iterative postorder, which is the classic "hard" traversal to implement from scratch. Level-order traversal is the only order that reveals depth information cheaply and is essential when "closest nodes first" matters, e.g., shortest-path-like queries on trees.
Worked example
Use the tree from the figure. Node keys and structure: ``` F / \ B G / \ \ A D I / \ C E ```
Preorder (N, L, R), recursive call trace — the sequence of `visit` calls in order:
| Step | Call | Action |
|---|---|---|
| 1 | PREORDER(F) | visit F |
| 2 | PREORDER(B) | visit B |
| 3 | PREORDER(A) | visit A (leaf, both children null) |
| 4 | PREORDER(D) | visit D |
| 5 | PREORDER(C) | visit C (leaf) |
| 6 | PREORDER(E) | visit E (leaf) |
| 7 | PREORDER(G) | visit G (no left child) |
| 8 | PREORDER(I) | visit I (leaf) |
Output: F B A D C E G I
Inorder (L, N, R) descends fully left before visiting anything: Output: A B C D E F G I — notice this is alphabetically sorted. That is not a coincidence for this example; it is the defining property exploited by Binary Search Trees, where inorder traversal always yields keys in sorted order regardless of shape.
Postorder (L, R, N), traced via the two-stack iterative method:
| Stack1 pops (push order R then L) | Stack2 (accumulates) |
|---|---|
| F → push G, push B | F |
| B → push D, push A | F, B |
| A (leaf) | F, B, A |
| D → push E, push C | F, B, A, D |
| C (leaf) | F, B, A, D, C |
| E (leaf) | F, B, A, D, C, E |
| G → push I | F, B, A, D, C, E, G |
| I (leaf) | F, B, A, D, C, E, G, I |
Popping stack2 (LIFO) reverses this to: A C E D B I G F — matching left-right-root order exactly.
Level-order, tracing the queue:
| Dequeue | Visit | Enqueue |
|---|---|---|
| F | F | B, G |
| B | B | A, D |
| G | G | I |
| A | A | (none, leaf) |
| D | D | C, E |
| I | I | (none, leaf) |
| C | C | (none, leaf) |
| E | E | (none, leaf) |
Output: F B G A D I C E — exactly depth 0, then depth 1, then depth 2 (A, D, I), then depth 3 (C, E). This matches the figure's annotation.
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Confusing preorder/postorder output order | Forgetting whether "N" comes first or last in N-L-R vs. L-R-N | Anchor to the mnemonic: pre = before children, post = after children, in = between |
| Iterative preorder pushes left before right | Stack is LIFO, so pushing in the "natural" left-then-right order pops right first | Push right child first, then left child, so left pops off top next |
| Attempting iterative postorder with one stack naively | Postorder needs to know a node's right subtree is fully done before visiting it — a single stack can't track that without extra state | Use the two-stack trick, or track a "last visited node" pointer with one stack |
| Using a stack for level-order | Stack gives LIFO (depth-first) order, not breadth-first | Use a queue — this is the single structural difference from DFS traversals |
| Forgetting the null/base case | Recursing into a non-existent child crashes or infinite-loops | Always check `node is null` first, before touching `node.left`/`node.right` |
| Assuming inorder gives sorted output for any binary tree | Sortedness is a property of Binary Search Trees, not of binary trees in general | Only rely on sorted inorder output when the BST invariant is guaranteed |
| Deep recursion on unbalanced trees | Recursive stack depth equals tree height; a 100,000-node "linked-list" tree can overflow the call stack | Prefer iterative traversal (explicit stack/queue on the heap) for untrusted or potentially skewed input |
Pioneers & history
Tree traversal as formal technique emerged alongside the broader development of graph and tree algorithms in the early era of computer science, when trees became the standard model for hierarchical data — expression parsing, file systems, and search structures. The recursive formulations trace directly to the recursive definition of trees themselves and to early work connecting recursion to stack-based computation.
Origins · 1 minJohn McCarthy and how this was discovered
McCarthy's LISP (1958) treated trees and lists as first-class recursive data, and functions operating on them — car, cdr, cons — became the substrate on which recursive tree traversal was naturally expressed. The equivalence between recursive procedure calls and explicit stack-based simulation, central to converting recursive traversals into their iterative forms, follows from the stack-frame model of procedure calls that matured alongside these ideas.
Where this matters
Compilers and interpreters parse source code into an abstract syntax tree and evaluate it in postorder: operands must be computed before the operator that combines them, exactly matching left-right-root order. File system utilities (like `du` or recursive directory printers) use preorder to print a directory before its contents, or postorder to compute sizes bottom-up before printing totals (this is how `du` accumulates directory sizes). Database indexes and <a class="wiki-link" href="/wiki/cs-algo-binary-search-trees" data-track="wiki_article.body.wiki_link">Binary Search Trees</a> rely on inorder traversal to produce sorted output for range queries — this is the mechanism behind `SELECT ... ORDER BY` on an indexed column in some database engines. Serialization formats (JSON, XML, protocol buffers) are built and parsed using preorder-like recursive descent, writing structure before content.
Level-order traversal underlies breadth-first search generalizations used in network routing (shortest hop-count paths), web crawlers (visiting pages by link-distance from a seed), and UI rendering engines that lay out widget trees level by level. Garbage collectors in managed-memory runtimes (JVM, .NET, V8) traverse object-reference graphs — trees in the common case, general graphs otherwise — using traversal strategies directly descended from these four patterns to mark reachable objects. Every balanced-tree data structure (AVL trees, red-black trees, B-trees) inherits these exact traversal algorithms unchanged; the traversal mechanics do not care how the tree is kept balanced, only that it is a binary (or, with minor generalization, -ary) tree.
Ask the tutor: How would iterative postorder traversal change if the tree were general (k-ary) instead of binary?
Why are there exactly three depth-first orders but only one breadth-first order?
Depth-first orders are defined by where the node itself sits relative to its two children in the visiting sequence: before both (preorder), between them (inorder), or after both (postorder) — three possible positions for one item among two groups. Breadth-first (level-order) traversal is organized by a completely different axis, depth, and has no left/right asymmetry to permute: you simply process depth 0, then depth 1, then depth 2, in the only order that makes sense for "closest first." There is no meaningful "reverse breadth-first" variant analogous to postorder, because breadth-first order is defined globally by depth, not locally by parent-child position.
Why is iterative postorder traversal considered the hardest of the three to implement with one stack?
In preorder and inorder, once you pop a node you either visit it immediately or already know you are done with it. In postorder, when you first reach a node you cannot visit it yet — you must first fully explore its right subtree, which itself may require descending arbitrarily deep — and only later, when you return to that same node, are you finally allowed to visit it. A single stack alone cannot distinguish "I am seeing this node for the first time" from "I've just returned from its right subtree" without extra bookkeeping, such as remembering the last node visited or tagging stack entries. The two-stack method sidesteps this by building the reverse of postorder first, then reversing it.
Does traversal order matter for the time complexity, or only for the output?
Only for the output, not the asymptotic time. All four traversals do a constant amount of work per node (one visit, at most two push/enqueue operations, one pop/dequeue), so all are Θ(n) regardless of order. The order changes which sequence the n values appear in, which matters enormously for correctness of the downstream task (sorted output, bottom-up evaluation, top-down printing) but not for the traversal's own running time.
Can these traversals be generalized beyond binary trees?
Yes. For a general (k-ary) tree, preorder and postorder generalize directly: preorder visits the node then recursively visits each child subtree in order; postorder recursively visits each child subtree in order, then the node. Inorder is specific to binary structure (visiting "between" two children only makes sense when there are exactly two), though some generalizations visit the node between a designated child and the rest. Level-order generalizes unchanged — enqueue all children of a dequeued node, regardless of how many there are.
Why does the recursive call-stack depth equal the tree's height rather than the number of nodes?
At any moment during recursive traversal, the call stack contains exactly the chain of ancestors from the root down to the node currently being processed — one stack frame per node on the root-to-current path. The longest such path in the tree is, by definition, the tree's height h. Once a recursive call returns, its stack frame is popped before any sibling subtree's frames are pushed, so frames from different branches never coexist. Hence the maximum simultaneous stack depth is h, not n, which is why balanced trees (h = O(log n)) traverse using only logarithmic auxiliary stack space.
How do these traversals relate to Binary Search Trees specifically?
A Binary Search Trees is a binary tree with the added invariant that every node's key is greater than all keys in its left subtree and less than all keys in its right subtree. Inorder traversal visits left subtree, then node, then right subtree — which, combined with the BST invariant, guarantees every key is visited in strictly increasing order. This single fact underlies sorted iteration, range queries, and the correctness of algorithms like finding the k-th smallest element via an augmented inorder walk. Preorder is used to serialize and later reconstruct a BST's exact shape (since it lists the root before its subtrees), and postorder is the safe order for deleting a whole BST bottom-up, since children are freed before their parent.
Is one traversal implementation (recursive vs. iterative) ever preferred by production systems?
Yes — the choice is a real engineering tradeoff, not just style. Recursive implementations are preferred when trees are guaranteed shallow (balanced) or when code clarity and provable correctness (via the induction argument) matter most, such as in compiler front-ends operating on syntax trees of bounded depth. Iterative implementations with an explicit heap-allocated stack or queue are preferred when trees can be adversarially deep or very large — for instance, processing untrusted, deeply nested JSON/XML input, where a recursive parser risks stack-overflow crashes that an attacker could trigger deliberately (a real class of denial-of-service vulnerability).