Skip to main content

Tree Traversal: Order and State

Traversal order answers a dependency question: does this node need to act before its descendants, between two branches, or after the children return? On a proper tree, a parent-child walk cannot cycle. That assumption stops being safe when the input is a graph or references can repeat.

FrontendInterviews.dev Updated

Recognize the Pattern

  • Carry bounds, depth, or a path from parent to child.
  • Combine child results after they finish.
  • Process nodes by depth when a level or nearest match matters.

Core Invariant

Preorder acts before children, inorder between them, and postorder after both. The required dependency determines the order.

Important Boundary

Inorder is sorted only for a valid binary search tree. A skewed tree can have height N and exhaust JavaScript's recursive call stack.

Binary tree with root 5 and children 2 and 8.
For root [5,2,8]: preorder is [5,2,8], inorder [2,5,8], and postorder [2,8,5].

Move the action, not the whole algorithm

The sorted-order claim requires a valid binary search tree; inorder on an arbitrary binary tree does not sort it. A DOM has an ordered list of children rather than binary left/right links, so preorder, postorder, and level order generalize directly, while this binary inorder template does not.

OrderAction positionUse
PreorderBefore childrenPropagate inherited state or serialize structure.
InorderBetween left and rightRead a binary search tree in sorted order.
PostorderAfter childrenCompute height or combine subtree states.
Level orderBy distance from rootGroup levels or find the shallowest match.

Template Assumptions

  • A finite binary tree with val, left, and right fields; null represents an absent child.
  • Collects all three traversal arrays and uses recursion; it is not safe for arbitrary depth.
JavaScript Template
function traversalOrders(root) {
  const preorder = [], inorder = [], postorder = [];
  function visit(node) {
    if (!node) return;
    preorder.push(node.val);
    visit(node.left);
    inorder.push(node.val);
    visit(node.right);
    postorder.push(node.val);
  }
  visit(root);
  return { preorder, inorder, postorder };
}
  1. On [5,2,8], enter 5: record it for preorder, then pause its frame while visiting 2.
  2. Node 2 records its preorder, inorder, and postorder actions; its two missing-child calls immediately return.
  3. Resume 5: record its inorder action, then pause again while visiting 8.
  4. After 8 finishes, resume 5 and record its postorder action. The parent frame survives while either child runs.

Step-by-Step: Tree traversal: enter, resume, and return

Step 1 / 5
528
Preorder
[5]
Inorder
[]
Postorder
[]

Enter root 5

Record 5 for preorder before visiting either child. The root's call pauses while its left subtree runs.

Inherited state versus returned state

  • Depth and ancestor constraints flow downward. For BST validation, pass a lower and upper bound; checking only immediate children misses ancestor violations.
  • Height and subtree answers flow upward. A missing child has a neutral contribution defined by the problem, not one universal value.
  • A level-order traversal keeps a queue and records its current end before processing a level. Children appended afterward belong to the next level.
  • Avoid shared mutable path arrays without push/pop restoration or deliberate copying. Sibling branches must not inherit each other's work.

An explicit stack for deep binary trees

When recursion depth is unsafe, collect root-right-left order with a stack and reverse it into left-right-root. This returns node objects, so a later bottom-up computation can associate child results with the exact nodes rather than their potentially repeated values.

Template Assumptions

  • A finite binary tree without cycles or shared child nodes.
  • Returns node objects in postorder with O(N) storage, avoiding recursive call-stack limits.
JavaScript Template
function postorderNodes(root) {
  if (!root) return [];
  const order = [];
  const stack = [root];
  while (stack.length) {
    const node = stack.pop();
    order.push(node);
    if (node.left) stack.push(node.left);
    if (node.right) stack.push(node.right);
  }
  return order.reverse();
}
  1. On [5,2,8], pop 5 and push left 2, then right 8.
  2. The stack pops 8 before 2, giving the collected order [5,8,2].
  3. Reverse it to [2,8,5]. Both children now precede their parent.
  • This template assumes a proper acyclic binary tree with no shared children.
  • It stores the entire order, using O(N) auxiliary space. It is not an O(H)-space frame-based traversal.
  • An explicit stack avoids recursive calls; it does not remove the memory needed to track traversal work.

Height and width are different costs

  • Visiting N nodes once takes O(N) time.
  • Recursive DFS uses O(H) call-stack space, excluding collected outputs. H can be N on a chain, not always log N.
  • BFS can hold a whole frontier: O(W) auxiliary queue space with a queue that releases processed entries, where W is maximum width. A head-index array retaining every visited node instead uses O(N) storage.
  • The traversalOrders example intentionally returns three N-element arrays, so its total auxiliary/output storage is O(N) even though recursion alone uses O(H).
  • JavaScript runtimes have finite call stacks. An algorithm can be linear-time and still overflow on a sufficiently deep input.

Mistakes and Counterexamples

Validate only the immediate children of a BST node.

Counterexample: Root 10 has right child 15, whose left child is 6. Each local comparison passes, but 6 violates the root's lower bound of 10.

Correction: Carry open ancestor bounds. When visiting 6, require 10 < value < 15, or validate the complete inorder sequence under the problem's duplicate rules.

Identify tree nodes by their values.

Counterexample: Two distinct leaves can both contain 7. A Map keyed by 7 overwrites one leaf's result or visited state.

Correction: Key per-node state by the node object or a unique node ID, not by its value.

Assume every recursive traversal uses logarithmic stack space.

Counterexample: A tree with one child at every node has height N, so visiting its N nodes nests N calls.

Correction: State O(H) stack space and use an explicit traversal when depth is unbounded.

Last-Minute Recap

  • Pick action order from the dependency.
  • A node's value is not its identity.
  • Separate output storage from traversal-stack storage.

Explain It in an Interview

  1. Which order fits inherited bounds, and which fits subtree heights?
  2. Why is an ordinary stack traversal not automatically postorder?
  3. How do height and width affect DFS and BFS memory differently?

Ready to Apply This Pattern?

Before coding, explain why this pattern fits and what must stay true at each step. Start with an easier problem, then test that reasoning on a harder variation.

Ordered from Easy to Hard. Premium access requirements still apply.

References