async and await while adding elements to ListT
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Using async and await while building a List<T> is common when values come from HTTP calls, databases, or file I/O. The important design question is not whether you can add to a list asynchronously, but whether the work should be sequential or concurrent and who owns the mutation of the list.
Sequential Await Keeps Ordering Simple
If each operation depends on the previous one, or if strict order matters, await each task and add the result afterward.
This is often the best answer when correctness and readability matter more than maximum concurrency.
Use Task.WhenAll for Independent Work
If the operations do not depend on each other, start them together and await them as a group.
This is usually the right pattern for independent I/O-bound work because it overlaps waiting time without forcing several tasks to mutate the same list concurrently.
List<T> Is Not Thread-Safe for Concurrent Mutation
If multiple tasks call Add at the same time, a normal List<T> can become inconsistent because it is not designed for concurrent writes.
If you truly must mutate a shared list from several concurrent contexts, synchronize access.
Even here, a cleaner design is often to collect results first and build the final list afterward.
Avoid List<T>.ForEach With Async Lambdas
This is a very common trap:
That starts async work, but it does not give you a clean way to await completion through ForEach. A normal loop or Task.WhenAll is almost always clearer.
Decide on Failure and Cancellation Behavior Up Front
A single failed operation raises another design question: should the whole list build fail, or should successful results still be kept?
- Sequential
awaitmakes fail-fast behavior obvious. - '
Task.WhenAllfails the combined await if any task fails.' - A custom result wrapper can preserve successes and failures together.
That policy choice often matters more than the raw syntax of await.
Common Pitfalls
A common mistake is letting multiple tasks add to the same List<T> without synchronization.
Another issue is using sequential awaits for fully independent operations and then paying unnecessary latency.
Developers also frequently use ForEach with async lambdas and assume the outer method waits for them. It does not.
Summary
- Use sequential
awaitwhen order matters or the work is dependent. - Use
Task.WhenAllwhen operations are independent and can run concurrently. - Do not mutate a shared
List<T>concurrently unless you synchronize access. - Prefer collecting results first rather than letting many tasks race to call
Add. - Avoid async lambda patterns that hide unawaited work.

