JavaScript
recursion
iteration
asynchronous programming
function conversion

How to convert sync and async recursive function to iteration in JavaScript

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Converting recursion to iteration in JavaScript usually means replacing the call stack with your own explicit data structure, most often an array used as a stack. The idea is the same for synchronous and asynchronous code, but asynchronous recursion needs one extra design decision: whether the iterative version should stay sequential or allow concurrency.

This is why a direct mechanical rewrite often fails. Recursion hides state inside call frames, while iteration requires you to store that state yourself.

How Recursive State Becomes Iterative State

In recursion, each call frame remembers:

  • where it is in the algorithm
  • local variables
  • what still has to happen after child calls return

An iterative rewrite has to store that information explicitly.

The simplest case is depth-first traversal of a tree or graph.

Converting Synchronous Recursion to Iteration

Suppose you have a recursive depth-first traversal:

javascript
1function walkRecursive(node) {
2  console.log(node.value);
3
4  for (const child of node.children) {
5    walkRecursive(child);
6  }
7}

The iterative version uses an explicit stack:

javascript
1function walkIterative(root) {
2  const stack = [root];
3
4  while (stack.length > 0) {
5    const node = stack.pop();
6    console.log(node.value);
7
8    for (let i = node.children.length - 1; i >= 0; i--) {
9      stack.push(node.children[i]);
10    }
11  }
12}

The reverse loop preserves the same left-to-right traversal order that the recursive version had.

That is the core transformation:

  • recursive call becomes stack.push(...)
  • returning from recursion becomes the next loop iteration

Another Simple Example: Factorial

Not every recursive function needs an explicit stack. Some only need accumulated state.

Recursive:

javascript
1function factorialRecursive(n) {
2  if (n <= 1) return 1;
3  return n * factorialRecursive(n - 1);
4}

Iterative:

javascript
1function factorialIterative(n) {
2  let result = 1;
3
4  for (let i = 2; i <= n; i++) {
5    result *= i;
6  }
7
8  return result;
9}

Here the recursive state collapses into a loop counter and an accumulator, so a custom stack is not necessary.

Converting Async Recursion to Iteration

Async recursion has the same structural issue plus await. Consider a recursive function that processes tree nodes one at a time:

javascript
1async function processRecursive(node) {
2  await saveNode(node.value);
3
4  for (const child of node.children) {
5    await processRecursive(child);
6  }
7}

If you want the iterative version to preserve the same sequential behavior, use a loop and stack with await inside the loop:

javascript
1async function processIterative(root) {
2  const stack = [root];
3
4  while (stack.length > 0) {
5    const node = stack.pop();
6    await saveNode(node.value);
7
8    for (let i = node.children.length - 1; i >= 0; i--) {
9      stack.push(node.children[i]);
10    }
11  }
12}

This still processes nodes sequentially, just without recursive call frames.

Async Iteration With Explicit Queues

Sometimes recursion really models a queue rather than a stack. Breadth-first logic is often clearer with an explicit queue:

javascript
1async function processBreadthFirst(root) {
2  const queue = [root];
3
4  while (queue.length > 0) {
5    const node = queue.shift();
6    await saveNode(node.value);
7
8    for (const child of node.children) {
9      queue.push(child);
10    }
11  }
12}

This is still iterative, but it preserves breadth-first rather than depth-first behavior.

When Async Iteration Can Be Concurrent

An async recursive function often uses await in a sequential way. When converting to iteration, you must decide whether to preserve that or intentionally introduce concurrency.

Sequential:

javascript
for (const item of items) {
  await process(item);
}

Concurrent:

javascript
await Promise.all(items.map(process));

These are not equivalent. The second version changes execution order, timing, and resource usage. So an async recursive rewrite is not just a syntax change. It is also a concurrency decision.

A General Recipe

When converting recursion to iteration:

  1. Identify the recursive state.
  2. Decide whether the traversal is stack-like or queue-like.
  3. Store the pending work explicitly.
  4. Preserve ordering deliberately.
  5. For async logic, decide whether the iterative version should stay sequential.

That recipe works for many tree, graph, parser, and filesystem-walking problems.

Common Pitfalls

One common mistake is converting recursion to a loop but forgetting the hidden post-call work. If the recursive function does more after child calls return, you may need a richer stack frame object, not just the node itself.

Another mistake is accidentally changing traversal order. A depth-first recursive function can become a different algorithm if you push children in the wrong order.

Async rewrites often fail by introducing unintended concurrency. Replacing recursive await logic with Promise.all may be faster, but it also changes behavior and can overload external services.

Finally, iteration avoids call stack overflow, but it does not automatically simplify the algorithm. Some recursive problems become clearer iteratively, while others become more verbose because you now manage all state manually.

Summary

  • Recursive state must be stored explicitly in an iterative rewrite.
  • Simple numeric recursion may become a loop with accumulators.
  • Tree and graph recursion often becomes an explicit stack or queue.
  • Async recursion can be rewritten iteratively with await inside the loop to preserve sequential behavior.
  • Be careful not to change traversal order or concurrency semantics by accident.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.