Running a task after all tasks have been completed
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When one task should run only after a group of other tasks finishes, you need a join point in your concurrency model. The exact API depends on the language, but the pattern is always the same: start the tasks, wait for all of them, then trigger the follow-up work.
The Coordination Pattern
Conceptually, the flow is:
- launch several tasks
- wait until every task completes
- run the dependent final task
This is different from chaining one task after another. Here, the first group may run in parallel, while the final task waits for the whole group to finish.
JavaScript Example with Promise.all
Promise.all resolves only when every promise succeeds. If one fails, the combined promise rejects immediately.
C# Example with Task.WhenAll
This is the same pattern in .NET: WhenAll creates a single awaitable task that completes when the whole set completes.
What to Decide Up Front
There are three important questions:
- should the final task run only if all earlier tasks succeed
- should failures be collected or should the whole flow stop on the first failure
- does the final task need the results of the earlier tasks or only the fact that they finished
Those choices affect the API you use and how you handle errors.
They also affect the shape of your final task. A final step that aggregates results is different from a final step that only publishes "batch complete."
If You Need "Always Run Cleanup"
Sometimes the final step must run even if one earlier task fails. That is no longer just "run after all succeed." It is closer to "wait for completion, then finalize."
In that case, you may still use Promise.allSettled, Task.WhenAll plus exception handling, or a barrier-like pattern that observes all outcomes before continuing.
The important design point is to distinguish:
- dependent success continuation
- unconditional finalization
They are not the same workflow.
Many bugs come from treating them as if they were interchangeable. A cleanup step may need to run after failures, while a reporting step may need successful results from every earlier task.
Common Pitfalls
The biggest mistake is starting all tasks and then forgetting to await the combined result. That makes the final task race ahead before the earlier work is actually done.
Another mistake is assuming "all completed" means "all succeeded." Many APIs distinguish between completion and successful completion.
A third issue is hiding exceptions inside the individual tasks and then wondering why the final task runs with incomplete or invalid state.
Summary
- Use a join primitive such as
Promise.allorTask.WhenAll. - Launch the independent tasks first, then wait on the combined result.
- Decide whether the follow-up task requires total success or just total completion.
- Propagate or handle failures deliberately instead of ignoring them.
- The final task should run after the group join point, not after the first task finishes.

