skip to content

Your recursive catalogue walk overflowed the stack in production — do you convert it to an explicit stack?

level: seniorimportance: should knowfreq 40%

answer

  1. is the depth legitimate or is it a cycle?
  2. measure the deepest real path first
  3. conversion moves the state, does not shrink it
  4. a fast crash beats a slow one
  5. ship a depth guard before any rewrite

basics

~20 s

First establish whether the depth was legitimate. If real data is genuinely that deep, an explicit stack moves the frames into memory that grows on demand and fixes it. If a cycle or bad parent link caused it, conversion only turns a fast crash into an out-of-memory.

solid answer

~50 s

The overflow is a symptom, and the conversion only treats one of its two possible causes, so I would measure before rewriting. Instrument the walk to report maximum observed depth and compare it against the deepest path the catalogue is supposed to have: if the data genuinely nests hundreds of levels, the recursion is structurally unsafe and an explicit stack is the right answer — the pending state moves from a fixed-size region reserved per thread into ordinary memory that grows on demand, and each entry is much smaller than a frame. If the legitimate maximum is thirty and we blew the stack, the depth is not the problem; a cycle or a mis-parented category is, and the rewrite would replace an immediate crash with an unbounded loop that eats the process instead. The cheap fix in both cases is a depth or visit guard that fails loudly with the offending path, and I would ship that first regardless of the rewrite.

go deeper

for a junior

Know that recursion depth is bounded by a fixed region reserved for the thread, and that a walk whose depth grows with the data can exhaust it. Say that you would find out how deep it actually went.

for a middle

Explain what the conversion moves rather than removes: the same depth-many entries relocated into memory that grows on demand, with smaller entries and an observable size. Name the two possible causes of the overflow.

for a senior

Demonstrate the diagnosis: measured maximum depth against the deepest legitimate path, a guard shipped first, and awareness that converting a cyclic traversal replaces a loud crash with a slow one.

for a principal

Own the choice between fixing this walk and fixing the data model that permits unbounded nesting, weighing a fleet-wide configuration change, the maintenance cost of hand-rolled stack code, and which invariants belong at write time.

## Diagnose before you rewrite A stack overflow in a recursive tree walk has two very different causes, and the conversion to an explicit stack helps with exactly one of them. **Cause one: the data really is that deep.** Somebody imported a supplier catalogue nested six hundred levels, or a customer built a category chain by hand. The recursion is structurally unsafe: its depth is a function of input you do not control, and it will fail again on the next big import. **Cause two: the traversal is not terminating.** A category whose parent link points at one of its own descendants makes the walk cyclic; the depth is unbounded not because the data is deep but because it is malformed. The measurement that separates them is cheap: track maximum depth reached and log the path when it exceeds a threshold. If the deepest legitimate path in your domain is on the order of tens and the walk reached thousands, you have cause two, and converting to an explicit stack is actively harmful — it takes a failure that is immediate, loud and attributable to one request and converts it into steadily growing memory, degraded latency for everything sharing the process, and eventually a much less informative death. ## What the conversion actually buys Suppose the depth is legitimate. The rewrite does not reduce asymptotic space: you still hold one entry per level of the current path, plus the unexplored siblings along it. What changes is where that state lives and how it fails. A thread's call stack is typically a contiguous region whose size is fixed when the thread is created, and exceeding it is a hard, immediate failure. Runtimes differ sharply here — some, such as Go and Erlang, give each lightweight task a small stack that grows on demand, while others, such as the JVM and native C-family threads, reserve a fixed size per thread — but in every case it is a smaller and less elastic budget than ordinary allocated memory. An explicit stack lives in that ordinary memory: it grows as needed, it holds compact entries rather than full frames with parameters, locals and a return address, and its size is something you can inspect, cap, and report on. So the honest claim is not "less memory" but "the same order of state, in a place with a far more forgiving limit and observable size". ## The alternatives worth pricing **Raise the stack budget.** The one-line change, and sometimes correct — but it is a per-thread cost multiplied by every thread on every instance in the fleet, it is configuration that travels badly between environments, and it buys a constant factor of headroom against a depth that scales with data. It postpones the incident rather than removing it. **Bound the depth and fail cleanly.** Cap the traversal, and on breach reject the operation with the offending path in the error. This does not process deep catalogues, but it converts a process-killing crash into one failed request with a diagnosis attached. It is usually the fastest thing to ship and it is compatible with every other option. **Fix the data model.** If nesting deeper than some level is meaningless for the business, enforce that at write time and the problem disappears at the source, permanently, for every reader of the catalogue rather than just this one walk. **Chunk the work.** Process the tree in bounded pieces with the frontier persisted between them, which also gets you restartability. More machinery than the incident probably justifies unless the walk is long-running anyway. ## Pricing the rewrite honestly The explicit-stack version costs real maintainability. Sibling order silently reverses unless children are pushed in reverse — a defect no aggregate test catches. Any work the recursion did after its calls needs a resume point encoded by hand. The result is control flow the next reader must simulate mentally instead of reading, in place of a recursion that read like the definition of the problem. That cost is worth paying when depth is unbounded by data you do not control and the traversal must still complete — and not worth paying for a tree whose depth is bounded by a schema constraint you could simply enforce. ## What a good answer sounds like Ship the depth guard today so the crash stops and the offending catalogue is named. Use its telemetry to decide which cause you actually have. If it is malformed data, fix the data and keep the guard as a permanent invariant — the recursion was never the bug. If the depth is legitimate, convert the one walk that matters, push the children in reverse, add a test that asserts visit order rather than a total, and leave the other twelve recursive walks in the codebase alone.

  • Why not just raise the stack size for that thread and move on?
    Because it is a constant amount of headroom against a depth that scales with data you do not control, so it postpones the incident rather than removing it. It is also a per-thread cost paid by every thread on every instance, and configuration that quietly differs between environments — the deep catalogue then fails only in production.
  • You convert the walk and it later runs out of memory instead. What went wrong?
    Almost certainly the traversal is not terminating — a cycle in the parent links, or nodes pushed more than once. The explicit stack removed the fixed limit that had been failing fast and honestly, so the malformed data now consumes the process slowly. The fix is a visit or depth guard, not more memory.

saying these in an interview costs you the question

  • Rewrites immediately without measuring the observed depth
  • Assumes an explicit stack cannot exhaust memory
  • Claims the conversion reduces the asymptotic space used
  • Treats a larger stack allocation as a permanent fix
  • Ignores that conversion turns a fast crash into a slow one

context