From recursion to iteration
In one line
Any recursion can be rewritten as a loop that manages its own stack (or, for tail recursion and DP, no stack at all), which removes the risk of stack overflow and gives you control over the order of work.
What it is
Recursion uses the language's call stack to remember unfinished work. You can do the same job with a loop and an explicit stack: push what you would have called, pop to process it, and store whatever a frame would have remembered (such as which child to visit next) in the stack entries.
Some recursions need no stack at all. A tail-recursive function, where the recursive call is the very last thing it does, can become a simple loop that updates its variables. Memoized recursion over states can become bottom-up tabulation that fills a table in order. Python does not optimise tail calls, so in Python these rewrites are how you handle large inputs.
Why interviewers ask
Interviewers ask for iterative tree traversals and iterative DFS to see whether you truly understand what recursion does behind the scenes. In production, recursion on user-controlled depth (a 100,000-node linked list, a degenerate tree, deeply nested JSON) can crash a server, so knowing how to convert is a practical safety skill.
How it works
- 1Identify what each recursive frame remembers: its arguments, local variables and where to continue after each call returns.
- 2Create a stack and push the initial call's state.
- 3Loop while the stack is not empty: pop a state, do the work that comes before the recursive calls, and push the states for the calls. Push children in reverse order if the visiting order matters.
- 4If work must happen after a child returns (in-order or post-order), either push a marker such as (node, visited=True) or use a pattern like 'go left until null, then pop'.
- 5For tail recursion, replace the call with reassigning the parameters and looping. For memoized recursion, fill a table from the base cases upward instead.
Worked example · Iterative in-order traversal of the tree 4 (2 (1, 3), 5)
- Start: node = 4, stack = [].
- Go left pushing: push 4, push 2, push 1. node becomes None. Stack [4, 2, 1].
- Pop 1, output 1. Its right is None. Stack [4, 2].
- Pop 2, output 2. Move to its right child 3; push 3, its left is None. Stack [4, 3].
- Pop 3, output 3. Right is None. Stack [4]. Pop 4, output 4. Move to right child 5, push 5. Pop 5, output 5.
- Stack empty and node is None: done. Output [1, 2, 3, 4, 5], the same as the recursive version, but the stack is a Python list on the heap that can hold millions of entries.
Picture it
Recursion is like following directions by asking a new helper at every crossroads to remember where you came from. Iteration with a stack is keeping your own notebook of 'places to come back to'. You do the same walk, but your notebook can be as long as you like, while the supply of helpers is limited.
Reference code
class Node:
def __init__(self, val, left=None, right=None):
self.val, self.left, self.right = val, left, right
def inorder_recursive(node, out):
if node:
inorder_recursive(node.left, out)
out.append(node.val)
inorder_recursive(node.right, out)
return out
def inorder_iterative(root):
out, stack, node = [], [], root
while stack or node:
while node: # go left, remembering the path
stack.append(node)
node = node.left
node = stack.pop() # the "return" from the left call
out.append(node.val)
node = node.right # then handle the right subtree
return out
root = Node(4, Node(2, Node(1), Node(3)), Node(5))
print(inorder_recursive(root, [])) # [1, 2, 3, 4, 5]
print(inorder_iterative(root)) # [1, 2, 3, 4, 5]
Both functions visit the same nodes in the same order. In the iterative version the stack holds exactly the nodes whose left subtree is in progress, which is what the recursive call stack would hold, but it lives in a Python list that is limited only by memory.
Key patterns
Explicit stack DFS
When: Pre-order tree traversal, graph DFS, flood fill
How: stack = [root]; pop, process, push children (right then left to visit left first).
Go-left-then-pop
When: In-order traversal of a binary tree
How: Push nodes while going left, pop and visit, then move to the right child.
Visited marker for post-order
When: Work must happen after all children finish
How: Push (node, False); when popped, push (node, True) and then the children; process the node when (node, True) pops.
Tail recursion to loop
When: The recursive call is the last action, as in gcd or binary search
How: Replace the call with updating the parameters inside a while loop.
Memoization to tabulation
When: Top-down DP hits recursion limits
How: Fill the table in dependency order from the base cases, often keeping only the last row or two values.
Real-world use
React Fiber
React's original reconciler walked the component tree recursively, so it could not pause mid-render. React Fiber (React 16) rewrote it as a loop over linked fiber nodes, which lets React pause, prioritise urgent updates and resume work.
Linux kernel code
Kernel threads on x86-64 have small fixed stacks (16 KB in modern kernels), so kernel code avoids deep recursion and uses loops and explicit lists when walking structures.
Garbage collectors
Mark phases in collectors like those in the JVM and V8 trace object graphs with explicit mark stacks or worklists rather than native recursion, because a long linked chain of objects would otherwise overflow the stack.
Languages with guaranteed tail calls
The Scheme standard requires proper tail calls, so tail recursion runs in constant stack space. Python deliberately does not do this, which is why Python code converts deep recursion to loops.
In a project
A document service stores nested folders and lets enterprise customers import folder trees. One customer's script created a chain 20,000 folders deep, and the recursive size calculator crashed the API worker with RecursionError.
- Replace the recursive folder_size(folder) with a loop over an explicit stack seeded with the root.
- Pop a folder, add its own file sizes to the total, and push its subfolders.
- For per-folder totals (post-order), push (folder, done=False), then re-push (folder, done=True) before its children, and sum children when the done entry pops.
- Add a test with a 100,000-deep chain and a test comparing results with the old recursive version on normal trees.
Impact: The calculator now handles any depth using heap memory, the crash class disappears for every customer, and the same explicit-stack helper is reused for permission checks and exports.
Trade-offs
Use it when
- Depth can be large or is controlled by user input
- You need to pause, resume or reorder work (scheduling, incremental rendering)
- The language has no tail-call optimisation and the recursion is a simple tail call
Avoid it when
- Depth is small and bounded, like a balanced tree, and recursion is clearer
- The recursion needs complex state after each child returns, and the explicit version becomes hard to read
- In an interview where clarity matters more, unless they ask for iteration or depth is a real risk
Cost: Converting does not change the time complexity. Extra space stays O(depth) for the stack, but on the heap instead of the call stack; tail-recursion loops and rolling-array tabulation can reduce it to O(1).
Common mistakes
- Pushing children in the wrong order, so the iterative pre-order visits right before left.
- Using a queue instead of a stack and getting BFS order by accident.
- Forgetting post-order work, such as summing children, because it has nowhere to go without a marker.
- Assuming Python optimises tail calls; it does not.
- Marking graph nodes visited at pop time versus push time inconsistently, causing duplicates on the stack.
Interview questions
Quick revision
- Recursion = loop + the call stack; replace it with your own stack.
- Pre-order: pop, visit, push right then left.
- In-order: go left pushing, pop, visit, go right.
- Post-order: (node, done) markers or two stacks.
- Tail call: turn into a while loop updating parameters.
- Top-down DP to bottom-up table removes recursion depth.
- Python has no tail-call optimisation; default limit 1000.
Check yourself
1. Iterative pre-order DFS pops a node and pushes its children. In which order should it push them to visit the left child first?
2. Does converting a recursive DFS to an explicit stack change its time complexity?
3. Which function can be turned into a loop with no stack at all?
4. What happens in CPython if you recurse 100,000 levels deep with the default settings?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.