Loading content...
You've now learned four tree traversals: preorder, inorder, postorder (all DFS-style), and level-order (BFS). Each visits every node exactly once, yet each produces a different order with different properties. The question that separates beginners from experts is: given a specific problem, which traversal should I use?
This isn't about memorizing a lookup table. It's about understanding the fundamental properties of each traversal and matching them to problem requirements. By the end of this page, you'll have a systematic framework for choosing the right traversal every time.
By the end of this page, you will have a complete decision framework for selecting tree traversals. You'll understand the key question to ask, see comprehensive use case mappings, and practice applying the framework to real problems. This is the synthesis page that ties all traversal knowledge together.
Let's begin with a comprehensive reference comparing all four traversals side by side:
12345678910111213141516171819202122232425262728293031323334
THE FOUR TREE TRAVERSALS═══════════════════════════════════════════════════════════════════════════ Sample Tree: A / \ B C / \ \ D E F ═══════════════════════════════════════════════════════════════════════════ Traversal │ Order │ Output │ Visit Position───────────────┼───────────────────────┼───────────────┼─────────────────────Preorder │ Root → Left → Right │ A,B,D,E,C,F │ Before childrenInorder │ Left → Root → Right │ D,B,E,A,C,F │ Between childrenPostorder │ Left → Right → Root │ D,E,B,F,C,A │ After childrenLevel-order │ Level by level │ A,B,C,D,E,F │ Top to bottom ═══════════════════════════════════════════════════════════════════════════ Key Observations:• Preorder: Root (A) is FIRST• Postorder: Root (A) is LAST• Inorder: Root (A) is in the MIDDLE (for this balanced tree)• Level-order: Nodes appear in top-to-bottom, left-to-right order ═══════════════════════════════════════════════════════════════════════════ Data Structure Used:• Preorder: Stack (explicit or implicit via recursion)• Inorder: Stack (explicit or implicit via recursion)• Postorder: Stack (explicit or implicit via recursion)• Level-order: Queue| Property | Preorder | Inorder | Postorder | Level-order |
|---|---|---|---|---|
| Order | VLR | LVR | LRV | Level by level |
| Root position | First | Middle* | Last | First |
| Approach | DFS | DFS | DFS | BFS |
| Data structure | Stack | Stack | Stack | Queue |
| Time | O(n) | O(n) | O(n) | O(n) |
| Space | O(h) | O(h) | O(h) | O(w) |
| Information flow | Top-down | Mixed | Bottom-up | Top-down by level |
h = tree height, w = maximum tree width. For a balanced tree: h ≈ log n and w ≈ n/2. For a skewed tree: h ≈ n and w ≈ 1. This means DFS is more space-efficient for wide, shallow trees while BFS is better for narrow, deep trees.
When facing a tree problem, ask yourself these three questions in order. The answers will guide you to the correct traversal:
123456789101112131415161718192021222324252627282930313233
TRAVERSAL DECISION FLOWCHART═══════════════════════════════════════════════════════════════════════════ ┌─────────────────────────────────────────┐ │ Need level-by-level processing? │ └──────────────────┬──────────────────────┘ │ ┌──────────────────┼──────────────────┐ │ YES │ │ NO ▼ │ ▼ ┌──────────────┐ │ ┌─────────────────────────────┐ │ LEVEL-ORDER │ │ │ Is this a BST needing │ │ (BFS) │ │ │ sorted access or │ └──────────────┘ │ │ ordering validation? │ │ └──────────────┬──────────────┘ │ │ │ ┌──────────────┼──────────────┐ │ │ YES │ │ NO │ ▼ │ ▼ │ ┌──────────────┐ │ ┌────────────────────────┐ │ │ INORDER │ │ │ Does processing │ │ └──────────────┘ │ │ current node DEPEND │ │ │ │ on children's results? │ │ │ └───────────┬────────────┘ │ │ │ │ │ ┌─────────────┼─────────────┐ │ │ │ YES │ │ NO │ │ ▼ │ ▼ │ │ ┌───────────┐ │ ┌───────────┐ │ │ │ POSTORDER │ │ │ PREORDER │ │ │ └───────────┘ │ └───────────┘ │ │ │ └───────────────────────┴───────────────┘Preorder (Root → Left → Right) is the right choice when you need to process a node before processing its children. Think of it as "do something, then delegate to children."
The defining characteristic: Top-down information flow. Parent's information is needed by children.
| Use Case | Why Preorder | Example |
|---|---|---|
| Cloning/copying a tree | Must create parent before attaching children | Deep copy of data structure |
| Serializing a tree | Write structure (parent) before content (children) | JSON serialization, file format |
| Printing tree structure | Print parent, then indent children | File system tree, org chart |
| Prefix expression | Operator before operands | Polish notation |
| Creating a copy with modification | Modify while copying top-down | Transform tree during traversal |
| Path problems (root to any node) | Build path as you descend | Find all root-to-leaf paths |
| Passing constraints down | Parent establishes constraints for children | BST validation with min/max |
| Computing depth/level | Pass depth + 1 to children | Find depth of target node |
12345678910111213141516171819202122232425262728293031323334353637
// PATTERN 1: Need to do something BEFORE recursingfunction process(node, info) { doSomething(node, info); // ← Process first process(node.left, newInfo); // Then recurse process(node.right, newInfo);} // PATTERN 2: Passing information DOWN the treefunction topDown(node, accumulatedValue) { // Use accumulated value at current node useValue(node, accumulatedValue); // Pass updated value to children topDown(node.left, update(accumulatedValue)); topDown(node.right, update(accumulatedValue));} // PATTERN 3: Creating/building somethingfunction create(node) { const newNode = { value: transform(node.value) }; // CREATE first newNode.left = create(node.left); // Then create children newNode.right = create(node.right); return newNode;} // CONCRETE EXAMPLE: Print paths from rootfunction printPaths(node, path = []) { if (!node) return; path.push(node.value); // PREORDER: add to path first if (!node.left && !node.right) { console.log(path.join(' → ')); } printPaths(node.left, [...path]); printPaths(node.right, [...path]);}Inorder (Left → Root → Right) is primarily associated with Binary Search Trees. For BSTs, inorder traversal produces elements in sorted order.
The defining characteristic: For BSTs, inorder visits nodes in ascending value order. For expression trees, inorder produces infix notation.
| Use Case | Why Inorder | Example |
|---|---|---|
| Sorted iteration of BST | Inorder = sorted order for BST | Print all elements sorted |
| K-th smallest element | Count in inorder, stop at k | Find median in BST |
| K-th largest element | Reverse inorder (RVL), stop at k | Find 3rd largest |
| BST validation | Check each value > previous | Is binary tree a valid BST? |
| BST to sorted array | Inorder gives sorted result | Flatten BST |
| Infix expression | Operator between operands | Standard math notation |
| Range queries in BST | Stop when out of range | Find all values between L and R |
| BST Iterator | On-demand next smallest | Merge K sorted BSTs |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
// PATTERN 1: Need sorted order from BSTfunction getSorted(bstNode) { const result = []; function inorder(node) { if (!node) return; inorder(node.left); // Left first result.push(node.value); // Then this node inorder(node.right); // Then right } inorder(bstNode); return result; // Sorted!} // PATTERN 2: Previous value comparison (BST validation)function isValidBST(node, prev = { value: -Infinity }) { if (!node) return true; if (!isValidBST(node.left, prev)) return false; // INORDER: check ordering if (node.value <= prev.value) return false; prev.value = node.value; return isValidBST(node.right, prev);} // PATTERN 3: K-th element (counting in order)function kthSmallest(node, k) { let count = 0; let result = null; function inorder(node) { if (!node || result !== null) return; inorder(node.left); count++; if (count === k) { result = node.value; return; } inorder(node.right); } inorder(node); return result;} // CONCRETE EXAMPLE: Find all nodes in range [low, high]function rangeSearch(node, low, high, result = []) { if (!node) return result; // Only go left if there might be values >= low if (node.value > low) { rangeSearch(node.left, low, high, result); } // Include this node if in range if (node.value >= low && node.value <= high) { result.push(node.value); } // Only go right if there might be values <= high if (node.value < high) { rangeSearch(node.right, low, high, result); } return result;}While inorder is most powerful for BSTs, it can be useful for general binary trees too—particularly for expression trees (infix notation) or when you need a symmetric traversal pattern. However, for non-BST trees, inorder doesn't have the magical 'sorted output' property.
Postorder (Left → Right → Root) is the right choice when processing a node requires information from its children. Think of it as "wait for children to report, then aggregate."
The defining characteristic: Bottom-up information flow. Children's information is needed by parent.
| Use Case | Why Postorder | Example |
|---|---|---|
| Tree deletion | Delete children before parent | Memory deallocation |
| Computing height | Height = 1 + max(children heights) | Find tree depth |
| Computing size | Size = 1 + left size + right size | Count all nodes |
| Computing sum | Sum = value + left sum + right sum | Sum all values |
| Tree diameter | Need heights to compute path through node | Longest path |
| Balance check | Compare children heights | Is tree balanced? |
| Subtree problems | Compute subtree property first | Largest BST subtree |
| Postfix expression | Operands before operator | RPN calculator |
| Expression evaluation | Evaluate operands before applying operator | Compute result |
| Maximum path sum | Compare paths through children | Binary tree max path |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
// PATTERN 1: Compute from children's resultsfunction computeFromChildren(node) { if (!node) return baseCase; const leftResult = computeFromChildren(node.left); const rightResult = computeFromChildren(node.right); // POSTORDER: use children's results to compute this node's result return combine(node.value, leftResult, rightResult);} // PATTERN 2: Aggregation (sum, max, count, etc.)function aggregate(node) { if (!node) return 0; const leftVal = aggregate(node.left); const rightVal = aggregate(node.right); return node.value + leftVal + rightVal; // Current depends on children} // PATTERN 3: Property checking that needs children's propertiesfunction checkProperty(node) { if (!node) return { valid: true, property: 0 }; const leftCheck = checkProperty(node.left); const rightCheck = checkProperty(node.right); // POSTORDER: check this node using children's info const thisValid = leftCheck.valid && rightCheck.valid && someCondition(leftCheck, rightCheck, node); return { valid: thisValid, property: computeProperty(...) };} // CONCRETE EXAMPLE: Tree diameter (max path between any two nodes)function diameter(node) { let maxDiameter = 0; function height(node) { if (!node) return -1; const leftH = height(node.left); const rightH = height(node.right); // POSTORDER: update diameter using children's heights maxDiameter = Math.max(maxDiameter, leftH + rightH + 2); return 1 + Math.max(leftH, rightH); } height(node); return maxDiameter;}Level-order (BFS) processes nodes level by level, from top to bottom. This is the only traversal that isn't naturally recursive—it uses a queue instead of a stack.
The defining characteristic: Processes nodes by their distance from the root. All nodes at depth d are processed before any node at depth d+1.
| Use Case | Why Level-Order | Example |
|---|---|---|
| Print tree level by level | Naturally groups by level | Visualize tree structure |
| Level averages/sums | Process complete levels | Average of levels |
| Minimum depth to leaf | First leaf found is min depth | Shortest path to leaf |
| Right/left side view | Last/first of each level | Side view of tree |
| Connect nodes at same level | Access siblings in queue | Populate next pointers |
| Zigzag level order | Alternate left/right by level | Zigzag traversal |
| Check completeness | Detect gaps level by level | Is complete binary tree? |
| Serialize for breadth structure | Level by level is natural for BFS | BFS-friendly serialization |
| Maximum width of tree | Count nodes per level | Widest level in tree |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
// PATTERN 1: Classic BFS with queuefunction levelOrder(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length > 0) { const node = queue.shift(); result.push(node.value); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } return result;} // PATTERN 2: Level-by-level processing (grouped)function levelsByLevel(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length > 0) { const levelSize = queue.length; // Key: snapshot current level size const currentLevel = []; for (let i = 0; i < levelSize; i++) { const node = queue.shift(); currentLevel.push(node.value); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(currentLevel); // Each level is separate array } return result;} // PATTERN 3: Early termination (find something on nearest level)function minDepthToLeaf(root) { if (!root) return 0; const queue = [{ node: root, depth: 1 }]; while (queue.length > 0) { const { node, depth } = queue.shift(); // BFS finds shortest path first! if (!node.left && !node.right) { return depth; // First leaf = minimum depth } if (node.left) queue.push({ node: node.left, depth: depth + 1 }); if (node.right) queue.push({ node: node.right, depth: depth + 1 }); }} // CONCRETE EXAMPLE: Right side view of treefunction rightSideView(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length > 0) { const levelSize = queue.length; for (let i = 0; i < levelSize; i++) { const node = queue.shift(); // Last node at this level = rightmost = visible from right if (i === levelSize - 1) { result.push(node.value); } if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } } return result;}Here's a comprehensive mapping from common problem types to the appropriate traversal. Use this as a quick reference when solving tree problems:
| Problem Type | Best Traversal | Reason |
|---|---|---|
| Clone/copy tree | Preorder | Create parent before children |
| Serialize tree | Preorder | Record structure during descent |
| Print directory structure | Preorder | Parent before contents |
| Path from root tracking | Preorder | Build path as you descend |
| Prefix notation | Preorder | Operator before operands |
| BST to sorted array | Inorder | BST inorder = sorted |
| K-th smallest in BST | Inorder | Count to k in sorted order |
| Validate BST | Inorder | Check each > previous |
| BST range query | Inorder | Prune + collect in order |
| Infix notation | Inorder | Operator between operands |
| Delete tree | Postorder | Delete children first |
| Compute height | Postorder | Need children heights first |
| Compute size | Postorder | Need children sizes first |
| Check balanced | Postorder | Compare children heights |
| Tree diameter | Postorder | Need heights for path calc |
| Subtree sum/max | Postorder | Aggregate from children |
| Postfix notation | Postorder | Operands before operator |
| Level averages | Level-order | Process level by level |
| Minimum depth | Level-order | BFS finds shortest first |
| Right/left side view | Level-order | Access last/first in level |
| Zigzag traversal | Level-order | Control direction per level |
| Connect same-level nodes | Level-order | Siblings in queue together |
| Maximum tree width | Level-order | Count nodes per level |
Some problems just need to visit all nodes—the order is irrelevant. In these cases, use whichever traversal you find easiest to implement (usually preorder for its simplicity):
1234567891011121314151617181920212223242526272829303132333435
// All four traversals give the same answer for these: // Count nodesfunction countNodes(node) { if (!node) return 0; return 1 + countNodes(node.left) + countNodes(node.right);} // Sum all valuesfunction sumTree(node) { if (!node) return 0; return node.value + sumTree(node.left) + sumTree(node.right);} // Find maximumfunction findMax(node) { if (!node) return -Infinity; return Math.max(node.value, findMax(node.left), findMax(node.right));} // Check if value existsfunction contains(node, target) { if (!node) return false; if (node.value === target) return true; return contains(node.left, target) || contains(node.right, target);} /* * For these problems, I used a pattern that's roughly preorder-ish, * but it doesn't really matter. I could swap the order of * processing and the answer would be the same. * * When you recognize an order-independent problem, just pick * the simplest implementation and move on. */When order doesn't matter, preorder is often simplest because you process the current node first, then recurse. It's the most 'natural' recursive pattern for many developers. But don't overthink it—any traversal works, so use what's comfortable.
Let's practice the decision framework. For each problem, identify the correct traversal before looking at the answer:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
PRACTICE: WHICH TRAVERSAL?═══════════════════════════════════════════════════════════════════════════ Problem 1: Find the lowest common ancestor of two nodes───────────────────────────────────────────────────────Think: Do I need children's info to determine if current node is LCA?Answer: POSTORDER - Need to check if target nodes are in left/right subtrees Problem 2: Mirror (invert) a binary tree─────────────────────────────────────────Think: When do I swap children?Answer: POSTORDER (swap after children are mirrored) OR PREORDER (swap first)Both work! Preorder is often cleaner. Problem 3: Find all nodes at depth K────────────────────────────────────Think: Do I need level-by-level processing?Answer: LEVEL-ORDER (stop at level K) OR PREORDER (pass depth parameter)Level-order is cleaner; preorder works too. Problem 4: Convert BST to Greater Sum Tree──────────────────────────────────────────(Each node's value = sum of all greater values + its value)Think: Need values in descending order...Answer: REVERSE INORDER (Right → Root → Left) - gives descending order Problem 5: Maximum depth of binary tree───────────────────────────────────────Think: Depth at current = 1 + max(depth of children)Answer: POSTORDER - need children's depths first Problem 6: Flatten binary tree to linked list (in preorder order)─────────────────────────────────────────────────────────────────Think: Result should be in preorder sequence...Answer: PREORDER - naturally produces the desired order Problem 7: All paths from root where sum equals target──────────────────────────────────────────────────────Think: Building path from root, accumulating sum...Answer: PREORDER - track path as you descend, check at leaves Problem 8: Number of nodes in each level────────────────────────────────────────Think: Need to group by level...Answer: LEVEL-ORDER - naturally processes level by level ═══════════════════════════════════════════════════════════════════════════ KEY INSIGHT: The traversal choice follows from understanding theinformation flow and dependencies in the problem!You now have a complete framework for choosing the right tree traversal. Let's consolidate the key decision factors:
123456789101112131415161718192021222324252627282930313233343536373839
TREE TRAVERSAL SELECTION CHEAT SHEET═══════════════════════════════════════════════════════════════════════════ USE PREORDER (Root → Left → Right) WHEN:────────────────────────────────────────✓ Creating/cloning tree structure✓ Serializing (parent info comes first)✓ Passing information DOWN (top-down)✓ Building paths from root✓ Prefix notation needed USE INORDER (Left → Root → Right) WHEN:───────────────────────────────────────✓ Need BST elements in sorted order✓ K-th smallest/largest in BST✓ Validating BST property✓ Infix notation needed✓ Any BST ordering operation USE POSTORDER (Left → Right → Root) WHEN:─────────────────────────────────────────✓ Computing from children's values (height, size)✓ Deleting/freeing tree memory✓ Result depends on subtree computations (bottom-up)✓ Postfix notation/expression evaluation USE LEVEL-ORDER (BFS) WHEN:───────────────────────────✓ Level-by-level processing required✓ Finding shortest path (in edges)✓ Computing level stats (averages, widths)✓ Side views of tree✓ Connecting same-level nodes WHEN ORDER DOESN'T MATTER:──────────────────────────✓ Simple aggregation (count, sum, max)✓ Existence checks✓ Default to preorder (simplest)Module Complete:
You have now mastered tree traversals—from understanding what traversal means, through implementing all three DFS traversals (preorder, inorder, postorder), to knowing exactly when to use each one. This knowledge forms the foundation for all tree algorithm work. Every tree problem you encounter will use one of these traversals as its backbone.
Congratulations! You now understand tree traversals at an expert level. You can implement preorder, inorder, and postorder both recursively and iteratively. You understand the unique properties of each (sorted BST output for inorder, bottom-up for postorder, top-down for preorder). Most importantly, you have a systematic framework for choosing the right traversal for any problem. This is knowledge that will serve you throughout your programming career.