All Posts

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 AlgorithmsAbstract Algorithms
ยทยท15 min read
Cover Image for Mastering Binary Tree Traversal: A Beginner's Guide
Share
AI Share on X / Twitter
AI Share on LinkedIn
Copy link

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.
TraversalVisit orderCommon use
Pre-orderRoot โ†’ Left โ†’ RightSerialize/copy a tree
In-orderLeft โ†’ Root โ†’ RightSorted output from a BST
Post-orderLeft โ†’ Right โ†’ RootDelete or free nodes safely
Level-orderTop to bottom by levelShortest 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.

PropertyDFS (Pre / In / Post)BFS (Level-order)
Data structureStack (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 treesO(1) โ€” only one node per level
Finds deepest node first?YesNo โ€” finds shallowest leaf first
Sorted BST output?In-order onlyNo
Best for level-by-level work?NoYes

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
MethodOutput sequence
Pre-orderA B D E C F G
In-orderD B E A F C G
Post-orderD E B F G C A
Level-orderA 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 shapeStack depthRisk
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:

ProblemBest traversalWhy
Validate BSTIn-orderExpects monotonically increasing sequence
Serialize/deserialize treePre-order + null markersCaptures structure unambiguously
Calculate tree heightPost-orderNeed children heights before parent
Find minimum depthLevel-orderBFS finds shallowest leaf first
Invert a binary treePre-order or post-orderEither works; process each node once

โš™๏ธ Complexity and Stack Safety

For a tree with n nodes and height h:

TraversalTimeSpace (recursive)Space (iterative)
Pre / In / PostO(n)O(h) stack framesO(h) explicit stack
Level-orderO(n)N/AO(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

PitfallSymptomFix
Forgetting the null base caseNullPointerException or infinite loopAlways check if node is None first
Using recursion on very deep treesStack overflowSwitch to iterative with explicit stack
Re-running full traversal inside a loopO(nยฒ) behaviorCache the result or restructure the loop
Confusing in-order with level-orderWrong output order on non-BSTDraw 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.
TraversalOutput sequence
Pre-order1 โ†’ 2 โ†’ 4 โ†’ 5 โ†’ 3
In-order4 โ†’ 2 โ†’ 5 โ†’ 1 โ†’ 3
Post-order4 โ†’ 5 โ†’ 2 โ†’ 3 โ†’ 1
Level-order1 โ†’ 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:

  1. "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.

  2. "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.

  3. "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.

  4. "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

  1. 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.

  2. 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.

  3. 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.



Abstract Algorithms

Written by

Abstract Algorithms

@abstractalgorithms