Recursive traversal of a deep rule tree overflows the call stack — what changes when you rewrite it with an explicit stack?
answer
- Recursion was already a stack
- Which region the frames lived in
- Last in, first out reverses you
- Push the one you want served later
- Postorder needs to know children finished
basics
~20 sYou take over the bookkeeping recursion did for you. An explicit stack holds pending nodes, and children are pushed in reverse so the left pops first. Preorder converts mechanically; postorder needs extra state to know both children finished.
solid answer
~50 sRecursion is already a stack — call frames in a small fixed region — so the rewrite relocates the stack rather than removing it. Asymptotically nothing changes: time stays O(n) and pending nodes stay proportional to the tree's height. Two things change in the code. Push order reverses, because a last-in-first-out stack pops in the opposite order to pushing: an iterative preorder pushes the right child then the left, so the left pops next. And only preorder converts mechanically — inorder needs a descend-left-pushing phase before each pop, and postorder needs a marker or second stack to tell "about to descend" from "both children done, emit now." The senior half is whether to rewrite at all: a balanced billion-node tree is about thirty deep, so an overflow says the shape is degenerate, and that may be the real defect.
code
pseudocode · 8 linespush(S, root)
while not empty(S):
node = pop(S)
visit(node)
if node.right != nil:
push(S, node.right) // pushed first...
if node.left != nil:
push(S, node.left) // ...so it pops after the leftgo deeper
Know that recursion keeps one frame per ancestor and that an explicit stack does the same job by hand, and that a stack returns items in the opposite order to how they went in.
Explain the preorder loop line by line, why the right child is pushed first, and why inorder and postorder need an extra phase or extra per-entry state rather than a moved emit line.
Diagnose before you rewrite: read the overflow as evidence about tree height, weigh the readability cost of the iterative form, and be able to name the silent mirrored-output bug the wrong push order produces.
Own the standard: decide when a codebase accepts unbounded-depth structures at all, whether depth is validated at ingest instead of defended at every walk, and how mixed recursive and iterative styles are justified to the people maintaining them.
## What recursion was doing for you A recursive traversal keeps, for every node on the path from the root to the node being handled, one call frame recording where to resume — which child comes next, what local values to restore. That is a stack, and its depth is the tree's height. The frames live in a per-thread region that is fixed and small at creation, so a tree whose height is thousands or tens of thousands deep exhausts it. Note what the overflow implies: height, not size. A bushy tree of a billion nodes stands about thirty levels tall and will never come close; an overflow means the structure is degenerate — long chains of single-child nodes — or its depth is not bounded by anything you control. ## The rewrite An explicit stack stores the same pending work in ordinary allocated memory, which grows far beyond the call region. Preorder is the easy case, and it is the fragment interviewers ask you to produce at the whiteboard: ``` push(S, root) while not empty(S): node = pop(S) visit(node) if node.right != nil: push(S, node.right) if node.left != nil: push(S, node.left) ``` ## Why the push order reverses This is the detail the question is really testing. A stack is last-in-first-out: whatever went in most recently comes out first. Preorder requires the left subtree to be fully processed before the right one, so the left child must be the **next** thing popped, so it must be the **last** thing pushed. Push right, then left. Get it backwards and the walk is perfectly well-formed — it simply produces the mirrored order, node-right-left, which is a silent output bug rather than a crash, and one that a symmetric test tree will not catch. ## The other two orders are not mechanical **Inorder.** You cannot emit a node when you first meet it; its whole left subtree must come out first. The standard loop pushes nodes as it walks down the left spine, then pops one, emits it, and moves to that node's right child to repeat. So the loop has two phases rather than one. **Postorder.** The node must be emitted only after *both* children are finished, so a bare stack of nodes is not enough state — meeting a node on the stack no longer tells you which of the two situations you are in. The two standard fixes are to store a flag or a last-visited pointer alongside each entry, or to run a mirrored preorder (node, right, left) accumulating into a second stack and read that out in reverse. It is worth stating the reversal identity precisely: postorder equals the *mirrored* preorder reversed. "Postorder is preorder reversed" without the mirror step is simply false, and it is a common confident wrong answer. ## What the rewrite does and does not buy - **Time:** unchanged, O(n). - **Asymptotic space:** unchanged — still proportional to height, still linear on a degenerate tree. The rewrite does not make a deep walk cheap; it makes it survivable, because the memory comes from a region that grows rather than one with a hard early ceiling. - **Per-entry cost:** usually lower than a call frame, which carries saved registers and return addresses, but this is a constant-factor argument, not an asymptotic one. - **Readability:** worse, and that is a real cost. A three-line recursive walk is obviously correct; the iterative postorder with a last-visited pointer is code reviewers get wrong. ## The judgment call Before rewriting, ask what the height actually is and why. Options, roughly in order of how often they are right: 1. **Bound the depth in the data.** If a fraud-triage rule tree is 40,000 levels deep, the generator that produced it is the defect. Fixing the shape fixes the walk and everything else that touches the structure. 2. **Rewrite the one hot walk iteratively** and leave the shallow ones recursive. Mixed styles are fine when the reason is written down next to the loop. 3. **Rewrite all of them** for uniformity, accepting the readability cost, when depth is genuinely unbounded input — user-supplied or externally generated structures you cannot constrain. 4. **Raise the stack size** — available on most platforms, but it moves the cliff rather than removing it, and it is per-thread, so a fleet of workers pays the memory for every one. Also note what does *not* help: this recursion is not tail-recursive — a node's work continues after its children return — so no automatic tail-call transformation applies to postorder or inorder, and only the very last call of preorder would qualify. "The compiler will optimize it away" is a wrong answer here. ## Failure modes to name - Pushing left before right and shipping a mirrored order. - Pushing empty children and dereferencing them after the pop. - Claiming the iterative version is asymptotically cheaper in space. - Applying the plain preorder loop to postorder by moving the emit line. - Rewriting every walk in the codebase when only one meets deep input.
- Does the iterative version have better space complexity than the recursive one?No — both hold pending work proportional to the tree's height, linear on a degenerate tree. What changes is where that memory comes from: call frames sit in a small fixed per-thread region, while an explicit stack allocates from a region that grows. The win is survivability and a smaller constant per entry, not a better asymptotic bound.
- Why does iterative postorder need more than a stack of nodes?Because meeting a node on the stack is ambiguous: you may be about to descend into it, or you may be returning with both children finished, and postorder must emit only in the second case. Recursion encoded that distinction in the resume point of the frame. Iteratively you restore it with a state flag or last-visited pointer, or by running a mirrored preorder into a second stack and reading it back in reverse.
- The tree overflowing the stack is 40,000 levels deep. What do you look at first?The shape, not the walk. A balanced tree of a billion nodes is about thirty levels deep, so that depth means long single-child chains or an unbounded generator. Fixing the structure fixes every pass over it, not just this one. Rewrite iteratively when depth is genuinely unbounded input you cannot constrain — and say so in a comment beside the loop.
- If push order is reversed by mistake, how does the failure show up?Not as a crash. The walk is still well-formed and still linear; it just emits node-right-left, the mirror of preorder. A symmetric test tree produces plausible output and hides it, so the bug typically surfaces downstream as a serialized form that rebuilds mirrored. Test with an asymmetric tree whose left and right sides are distinguishable.
A stack of tickets face-down: the last one you drop on the pile is the first one you pick up. To be served left-first, drop the right ticket down before the left one.
saying these in an interview costs you the question
- Claims the iterative version has lower space complexity
- Pushes the left child first and expects preorder
- Says postorder is plain preorder reversed
- Expects tail-call optimization to fix a tree walk
- Raises the stack size and calls the depth problem solved