JavaScript: concurrency without parallelism

See how async functions interleave on a single thread and finish together under Promise.all.

JavaScript
const log = [];

async function worker(name, steps) {
  for (let i = 1; i <= steps; i++) {
    log.push(name + i);
    await null;
  }
  return name;
}

Promise.all([worker("A", 3), worker("B", 2), worker("C", 3)]).then((names) => {
  console.log(log.join(" "));
  console.log(names.join(","));
});
Output
A1 B1 C1 A2 B2 C2 A3 C3
A,B,C

Explanation

Concurrency means several tasks make progress during the same period. Parallelism means they run at the same instant on different cores. JavaScript's async functions give concurrency on one thread: each await suspends a function and lets others run.

In the example every worker records a step and then awaits. The first step of each worker runs when it is called, and the remaining steps are scheduled as microtasks, so the workers take turns. Promise.all collects the results in the order the promises were given, not the order they finished. There is no parallel execution here, so no data races, but CPU-bound work still blocks everyone.

Explore the dry run
Guided walkthroughReady
1const log = [];
2
3async function worker(name, steps) {
4 for (let i = 1; i <= steps; i++) {
5 log.push(name + i);
6 await null;
7 }
8 return name;
9}
10
11Promise.all([worker("A", 3), worker("B", 2), worker("C", 3)]).then((names) => {
12 console.log(log.join(" "));
13 console.log(names.join(","));
14});

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Each worker records a step, then yields at the await.—
—
Suspend this function; other ready workers run before it continues.workers take turns
—
All three start in order, each running up to its first await.workers take turns
—
A, B, and C take turns until B (two steps) is done.workers take turns
A1 B1 C1 A2 B2 C2 A3 C3
Results arrive in the order of the input array.workers take turns
A1 B1 C1 A2 B2 C2 A3 C3
A,B,C

Cost and common mistakes

O(total steps) work on one thread.

  • await does not create a thread. A tight CPU loop inside any of these workers would stop all the others.
  • If one promise in Promise.all rejects, the combined promise rejects at once; use Promise.allSettled to collect every outcome.

Try it yourself

What would the log look like with only workers A (3 steps) and C (3 steps)?

Reveal answer and reasoning

A1 C1 A2 C2 A3 C3, because they alternate turn by turn.

Further reading