Clean pattern to manage multi-step async processes on a tree
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Multi-step async workflows over a tree become messy when the structure of the data and the structure of the control flow are mixed together. A clean pattern is to model each tree node as data, define each processing step as a pure async stage, and let one orchestration function walk the tree and combine results. That keeps traversal, business logic, concurrency policy, and error handling separate instead of tangling them in nested promises or callbacks.
Separate Node Data From Workflow Steps
The first step is to make the tree itself boring. It should just describe the hierarchy.
Then define the async steps independently. For example, imagine that every node must be:
- fetched from a remote system
- validated
- stored
Those are workflow stages, not tree-structure concerns.
A Small Pipeline Per Node
A clean way to express node processing is one async function that takes a node and returns a result object.
This isolates the node-level business logic from the traversal strategy.
Orchestrate Tree Traversal Separately
Now build one orchestrator that walks the tree. A depth-first recursive approach is easy to reason about.
That gives you sequential execution, which is often the simplest and safest default.
Add Controlled Concurrency, Not Unlimited Concurrency
A common mistake is to switch from sequential recursion to Promise.all everywhere and accidentally flood the database or remote API. If sibling nodes can run in parallel, do it deliberately and usually with a limit.
A simple sibling-parallel version looks like this:
That is fine for small trees, but for large ones you usually want a concurrency limiter rather than unbounded fan-out.
Make Error Policy Explicit
Another place tree workflows become ugly is error handling. Decide early whether:
- one node failure stops the whole tree
- child failures are collected and processing continues
- certain errors are retryable while others are terminal
A result wrapper makes that explicit.
Once failures become data instead of uncaught control flow, aggregation gets much cleaner.
The Pattern In One Sentence
The clean pattern is:
- data model for the tree
- pure async pipeline for one node
- separate traversal orchestrator
- explicit concurrency policy
- explicit error policy
That structure scales well because each concern can change independently.
Common Pitfalls
- Mixing traversal logic and business-step logic in one large recursive function.
- Using
Promise.allblindly and overwhelming external systems or worker capacity. - Hiding error-handling policy inside ad hoc
tryblocks spread across the tree walker. - Passing mutable shared state through the whole recursion when result aggregation would be clearer.
- Treating the tree shape as if it must dictate the workflow stages rather than just the traversal order.
Summary
- Keep the tree structure as data and the async work as a separate node-level pipeline.
- Use one orchestrator function to walk the tree and combine results.
- Start with sequential recursion and add parallelism only when the workload justifies it.
- Make failure handling a visible policy instead of an accident of nested async code.
- Clean async tree processing comes from separation of concerns, not from more clever recursion.

