I've watched plenty of engineers explain binary tree traversals on a whiteboard and then freeze five minutes into an unfamiliar tree problem. The gap isn't conceptual. They know what preorder, inorder, and postorder mean. They can sketch a BST. What they can't do is predict, in their head, how the call stack will branch when a recursive function meets a tree's shape.
That's the real skill tree interview problems test. And once you can do it, the patterns stop feeling like 14 separate things to memorise.
TL;DR
- Tree problems test whether you can mentally simulate the call stack's shape before writing code.
- The six traversal patterns split along one axis: which direction does information flow?
- Down through parameters, up through return values, or sideways across siblings.
- Get the direction right and the solution writes itself.
- Get it wrong and you spend the interview debugging recursion that looks correct at every node.
Why arrays don't prepare you for trees
When you scan an array, you hold the whole traversal in your head as a walk from left to right. One element at a time. Your mental model is a line.
Trees break that completely. At every node, execution splits into two branches, and each of those branches splits again. The call stack grows in a shape you can't flatten. To predict what your code does, you have to think in the shape of the tree itself.
This is why someone can solve medium array problems comfortably and still stumble on easy tree problems. The mental simulation load is qualitatively different. You're tracking which branch you're in, what state you carried down, and what values are coming back up.
The call stack is a concrete thing, not a metaphor
Before you can simulate tree recursion, you have to know what's physically happening in memory when a recursive function runs. The call stack is a real structure in your program's memory, not a teaching analogy.
Every function call pushes a stack frame. The frame holds the function's local variables, parameters, and the return address. When the function returns, the frame pops and the frame below resumes exactly where it left off.
Walk through factorial(4) and the stack looks like this at maximum depth:
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
| factorial(1) | <- top, currently executing
| factorial(2) | <- waiting on factorial(1)
| factorial(3) | <- waiting on factorial(2)
| factorial(4) | <- original call, waiting on result
Each frame keeps its own n. Nothing shared. When factorial(1) returns 1, that frame pops. factorial(2) resumes, computes 2 * 1, returns 2. The chain unwinds until factorial(4) returns 24.
I'd recommend stepping through this in a debugger once. Watching frames push and pop in real time teaches more about recursion in ten minutes than an hour of reading explanations. Most engineers I see prep have never done this, and it changes how you predict what recursive code will do.
Now extend the same idea to a binary tree. Each node makes two recursive calls instead of one. The stack still pushes and pops frame by frame, but the shape it traces is no longer a straight line down and back up. It's the branching shape of the tree itself. Predict that shape correctly and the solution becomes mechanical.
The information flow diagnostic
Every tree problem maps to one of six traversal patterns. The patterns split cleanly when you ask a single question:
Which direction does information have to flow to solve this problem?
- Down (preorder). State is carried from parent to children through function parameters. The node is processed before its children. Useful when each node's computation depends on what came before it on the path from the root.
- Up (postorder). Children are processed first, and their results bubble up through return values. The node is processed after its children. Useful when the answer at each node depends on its subtrees.
- Sideways (level order). Nodes at the same depth are processed together before moving to the next level. The call stack doesn't help here. You want a queue.
That diagnostic alone resolves most pattern-selection questions in under 30 seconds.
The six patterns are refinements of the three directions:
- Stateless preorder: pure top-down. No accumulator. No return value carrying anything.
- Stateful preorder: top-down with a shared accumulator that mutates on descent.
- Stateless postorder: pure bottom-up. Each subtree's result is a function of its children's results.
- Stateful postorder: bottom-up with a global side effect updated from subtree results.
- Root to leaf path: top-down accumulation, evaluation only at leaves.
- Level order: queue-based BFS, not recursion at all.
Stateful postorder is the hardest of the six. The return value at each node is what the parent needs, but the answer to the problem lives in a global variable updated as a side effect. Tree diameter is the textbook example. Each call returns subtree height, but the diameter (the actual answer) accumulates separately as the maximum of left_height + right_height seen at any node.
BST validation, the recursion that looks correct but isn't
A binary search tree adds one invariant to the binary tree: every value in the left subtree is smaller than the node, every value in the right subtree is larger. That single rule lets you do things on a BST that aren't possible on a general binary tree.
The most natural use of the invariant is inorder traversal, which visits BST nodes in sorted order. Left subtree, then node, then right subtree. The traversal physically walks the sorted sequence.
BST validation is where most people write the wrong recursion. The instinctive version checks each node against its direct children.
# Wrong: checks only direct parent-child constraints
def is_valid_bst(root):
if not root:
return True
if root.left and root.left.val >= root.val:
return False
if root.right and root.right.val <= root.val:
return False
return is_valid_bst(root.left) and is_valid_bst(root.right)
Consider the tree [10, 5, 15, null, null, 6, 20]. Every direct parent-child pair is locally fine. 5 < 10. 15 > 10. 6 < 15. 20 > 15. The naive recursion returns True. But the tree is invalid, because 6 sits in the right subtree of 10 and 6 < 10 violates the BST invariant against its ancestor.
The correct recursion uses inorder traversal and tracks the previously visited value. If the inorder sequence is strictly increasing, the tree is a valid BST.
def is_valid_bst(root):
prev = float('-inf')
def inorder(node):
nonlocal prev
if not node:
return True
if not inorder(node.left):
return False
if node.val <= prev:
return False
prev = node.val
return inorder(node.right)
return inorder(root)
On the same tree, inorder visits 5, 10, 6, 15, 20. When it reaches 6 with prev = 10, the check 6 <= 10 fires and returns False. The mechanism that makes this work is that inorder converts the ordering invariant into a single forward-walking check across the entire tree. The naive version only checks two-node relationships and misses every ancestor violation.
The four BST-only patterns
The ordering invariant opens four traversal strategies that don't exist on general binary trees:
- Sorted traversal: use inorder for problems where the BST's ascending order is the answer. Validation, conversion to a sorted array, conversion to a doubly linked list.
- Reverse sorted traversal: visit right, then node, then left, for descending order. Kth largest, ranking, enriched sum trees.
- Range postorder: prune entire subtrees by comparing the node's value against a range. If the node sits below the range, skip its left subtree entirely. This is where BSTs get their logarithmic performance on range queries.
- Two pointer on BST: run a forward iterator (inorder) and a reverse iterator (reverse inorder) simultaneously. Two sum on a BST, finding the median, pair sum problems.
For heaps, two patterns cover most of what interviews ask. Top K elements maintains a heap of size K (a min heap to track the K largest, counterintuitively). The comparator pattern uses custom ordering when "priority" isn't the raw value, which covers K most frequent, K smallest pairs, K closest points, and K way merge.
The mistakes I see most often
Across the engineers I help prep, the same handful of mistakes show up:
- Confusing preorder and postorder state. You try to compute tree height by passing depth down through parameters, when height has to aggregate up from children through return values.
- Forgetting to capture the return value. Writing
postorder(node.left)without assigning the result. The recursive call executes, but the computed information evaporates. - Mixing global and local. The function returns a local value (the subtree's result for the parent) while a global accumulator holds the actual answer. Diameter is the classic case. Confuse the two and the recursion looks right at every node but the answer comes out wrong.
- Using recursion for level order. Level order is breadth first by nature, and recursion is depth first. You can simulate level order with recursion plus a depth parameter, but a queue-based BFS is cleaner and what interviewers expect.
- Skipping the subproblem definition. Before writing any recursive function, answer in one sentence what
f(node)returns. If you can't, the recursion will fail. - Forgetting the null base case. Null children show up everywhere in binary trees. A leaf has two null children. Forgetting to handle them is the most common runtime error in tree solutions.
- State not undone on backtrack. In root to leaf path problems, you append to a path list on descent. If you don't pop on return, the path accumulates stale entries from sibling branches.
How to practice the simulation skill
Mental simulation isn't built by solving more problems. It's built by predicting the call stack's shape before running the code, and then verifying the prediction.
Take a problem you've solved before. Cover the solution. Sketch the tree. Predict the call sequence on paper. Now run the solution in a debugger and step through frame by frame. Note where your prediction diverged from what actually happened.
That divergence is the gap interviews test under pressure. Every problem you do this on builds the simulation skill directly. After about ten rounds, the prediction step stops being a separate exercise and starts running automatically as you read the problem statement.
That's the gap I built Codeintuition to close. The binary tree course walks through the call stack frame by frame on every pattern, so the simulation becomes something you've already done dozens of times rather than something you're constructing from scratch under interview pressure.
I wrote a longer version on my own blog that covers the BST sorted traversal family and the heap patterns I skipped here.
Which pattern was the one that finally made the call stack visible in your head? Mine was stateful postorder, the first time I traced diameter by hand.
Top comments (0)