async programming
JavaScript
await in loop
asynchronous loops
JavaScript tutorials

How to use await in a loop

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Using await in a loop is valid JavaScript, but the correct loop style depends on whether tasks must run sequentially or can run in parallel. Many bugs come from mixing those two goals without realizing it. The right question is not "can I use await in a loop". The right question is "do I want each iteration to wait for the previous one".

Use for...of for Sequential Async Work

If the operations must happen in order, for...of with await is the cleanest option.

javascript
1async function processItems(items) {
2  for (const item of items) {
3    const result = await fetch(`/api/${item}`);
4    console.log(await result.text());
5  }
6}

This waits for each request to finish before starting the next one. That is correct when:

  1. order matters
  2. the server should not be flooded with parallel requests
  3. later work depends on earlier results

Do Not Expect forEach to Await Properly

This is a common mistake:

javascript
items.forEach(async item => {
  await doWork(item);
});

forEach does not await the async callback. The surrounding function continues immediately, so this pattern often leads to unfinished work, confusing logs, or uncaught timing assumptions.

If you need sequencing, use for...of. If you need parallelism, build promises explicitly.

Use Promise.all for Parallel Work

If iterations are independent, parallel execution is often faster.

javascript
1async function processItems(items) {
2  const promises = items.map(item => fetch(`/api/${item}`));
3  const responses = await Promise.all(promises);
4  return responses;
5}

This starts all requests immediately and waits for all of them. It is usually the right pattern when order of completion does not matter and the workload can safely run concurrently.

Use Promise.allSettled When Partial Failure Is Acceptable

Promise.all rejects as soon as one promise rejects. If you want to collect every result, including failures, use Promise.allSettled.

javascript
1async function loadAll(urls) {
2  const results = await Promise.allSettled(
3    urls.map(url => fetch(url))
4  );
5  console.log(results);
6}

This is useful for dashboards, batch jobs, and cleanup operations where one failure should not hide the rest.

For workloads with external APIs, this often produces a better operational picture than failing fast, because you can log which calls failed while still returning the successful results.

The same pattern is useful for cleanup jobs, migration scripts, and dashboards where "best effort plus reporting" is more valuable than one all-or-nothing failure.

for await...of Is for Async Iterables

for await...of is a different feature. It is meant for async iterables or streams, not ordinary arrays of promises by default.

javascript
1async function* pages() {
2  yield "page-1";
3  yield "page-2";
4}
5
6async function run() {
7  for await (const page of pages()) {
8    console.log(page);
9  }
10}

This is great for paginated APIs, streaming sources, or generator-based pipelines.

Choose by Concurrency Intent

A practical rule:

  1. for...of plus await for sequential work
  2. Promise.all for parallel work
  3. Promise.allSettled for parallel work with tolerated failures
  4. for await...of for async iterables

Most confusion disappears once the concurrency model is chosen explicitly.

Common Pitfalls

  • Using await inside forEach and expecting the outer flow to wait.
  • Running sequential loops when the work could safely run in parallel.
  • Using Promise.all when one failure should not cancel the whole batch.
  • Confusing arrays of promises with async iterables.
  • Forgetting that parallelism can overload a server or API if not controlled.

Summary

  • 'await in a loop is fine when sequential behavior is intentional.'
  • Use for...of for ordered async work.
  • Use Promise.all or Promise.allSettled for parallel tasks.
  • Use for await...of only for async iterables and streams.
  • Pick the loop pattern based on concurrency semantics, not syntax preference.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

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

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.