# Parallel vs Sequential Async Operations

## 1. Sequential: Each Step Waits for the Previous One

```js
async function sequential() {
  const a = await delay(100, "a"); // starts, waits 100ms
  const b = await delay(100, "b"); // does NOT start until `a` is done — another 100ms
  const c = await delay(100, "c"); // does NOT start until `b` is done — another 100ms
  return [a, b, c];
}
// total time: ~300ms
```

Each `await` blocks the **function** (not the whole program — see the Async/Await topic) from proceeding until that specific operation finishes, before even starting the next one.

## 2. Parallel: All Operations Start Immediately

```js
async function parallel() {
  const promiseA = delay(100, "a"); // starts immediately, NOT awaited yet
  const promiseB = delay(100, "b"); // starts immediately too — overlaps with A
  const promiseC = delay(100, "c"); // starts immediately too — overlaps with A and B

  const [a, b, c] = await Promise.all([promiseA, promiseB, promiseC]); // wait for all THREE at once
  return [a, b, c];
}
// total time: ~100ms — all three ran concurrently
```

The critical detail: calling `delay(100, "a")` **starts** the operation immediately, whether or not you `await` it right away. Sequential code accidentally forces this waiting by awaiting each call before starting the next.

## 3. The Bug Pattern: Accidental Serialization

```js
// LOOKS like it should be fine, but is needlessly sequential:
async function loadAllUsersSlow(ids) {
  const users = [];
  for (const id of ids) {
    const user = await fetchUser(id); // each iteration waits for the PREVIOUS one
    users.push(user);
  }
  return users;
}

// Fixed — starts all requests immediately, waits for all together:
async function loadAllUsersFast(ids) {
  const promises = ids.map(id => fetchUser(id)); // all fetches START here, immediately
  return Promise.all(promises);                    // NOW wait for all of them together
}
```

`loadAllUsersSlow` with 5 users taking 100ms each takes ~500ms total. `loadAllUsersFast` takes ~100ms total (limited by the slowest single request, not the sum of all of them).

![Sequential bars laid end to end sum to 300ms; parallel bars stacked at the same start point finish in 100ms](diagrams/01-timelines.svg)

## 4. When Sequential Is Actually Required

Sequential execution is necessary — not just accidental — when a later step genuinely **depends on** an earlier step's result:

```js
async function checkoutFlow() {
  const cart = await getCart();                 // step 2 needs cart's contents
  const total = await calculateTotal(cart);       // step 3 needs the calculated total
  const receipt = await chargeCard(total);         // each step's INPUT comes from the previous OUTPUT
  return receipt;
}
```

Also required when operations must not overlap for correctness (writing to the same file, respecting a strict rate limit, maintaining a specific order of side effects).

## 5. Partial Parallelism — Mixing Both

```js
async function loadPage(userId) {
  // These two are INDEPENDENT of each other — run them in parallel:
  const [user, settings] = await Promise.all([
    fetchUser(userId),
    fetchSettings(userId),
  ]);

  // This DEPENDS on `user` — must run sequentially after the above:
  const recommendations = await fetchRecommendations(user.preferences);

  return { user, settings, recommendations };
}
```

Identify genuine dependencies first; run everything else concurrently.

## 6. Controlled Concurrency (Not Fully Parallel, Not Fully Sequential)

Running 10,000 requests all at once via `Promise.all` can overwhelm a server or hit rate limits. A common middle ground processes a **limited batch size** at a time:

```js
async function processInBatches(items, batchSize, asyncFn) {
  const results = [];
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    const batchResults = await Promise.all(batch.map(asyncFn)); // parallel WITHIN a batch
    results.push(...batchResults);
    // sequential BETWEEN batches — waits for one batch before starting the next
  }
  return results;
}
```

## Under the Hood: How `Promise.all` Actually Tracks Completion

`Promise.all` isn't implemented as "poll every promise until they're all done" — the spec algorithm (`PerformPromiseAll`) sets up a real counter and a fixed-size results array up front, then lets each individual promise's own resolution mechanism (the `[[PromiseFulfillReactions]]` machinery from the Promises topic) do the actual work of reporting back.

### The algorithm, precisely

1. Allocate a results array sized to the input list, and a counter initialized to the number of promises **still remaining**.
2. For each input promise at index `i`, attach a `.then()` reaction that, when it fires, writes the resolved value into `results[i]` and **decrements the counter**.
3. When the counter reaches zero, resolve `Promise.all`'s own returned promise with the completed `results` array.
4. Separately, attach a rejection reaction to **every** input promise that, on first firing, immediately rejects `Promise.all`'s returned promise with that reason — and does nothing on any subsequent rejection (the returned promise can only settle once, per the Promises topic's single-settlement guard).

```
promises: [P0, P1, P2]   counter starts at 3
P1 resolves first  → results[1] = value, counter → 2
P0 resolves second  → results[0] = value, counter → 1
P2 resolves third    → results[2] = value, counter → 0  → Promise.all's promise resolves with results
```

### Why results preserve input order despite resolving out of order

Section 1's examples show `Promise.all([a, b, c])` giving back `[a, b, c]` in order, even though `b` might genuinely finish first. This is *why*: each reaction writes to a **fixed index** (`results[i]`, based on the promise's position in the input array, not the order it resolves in) — order preservation isn't a sorting step at the end, it's baked into which array slot each reaction targets from the start.

### Why one rejection is final even though other promises keep running

The other, still-pending promises in the array are **not cancelled** when one rejects — nothing in JavaScript's Promise model supports cancelling a promise (that's precisely the gap the Abort Controller topic's `AbortController` exists to fill at the operation level, not the Promise level). They continue running to completion in the background; their eventual results are simply never read, because `Promise.all`'s own promise already settled and its single-settlement guard makes any further resolve/reject attempts on it a no-op.

## 7. Common Pitfalls

1. **Using `await` inside a `for` loop when operations are independent** — the single most common accidental-serialization bug; fix with `.map()` + `Promise.all`.
2. **Assuming `Promise.all` limits concurrency** — it doesn't; it starts everything at once, which can overwhelm a downstream service with truly large arrays (see section 6 for the fix).
3. **Forcing sequential execution "to be safe" when there's no real dependency** — needlessly slows things down; only serialize when a genuine ordering or dependency requirement exists.
4. **Missing a real dependency and parallelizing anyway** — running `chargeCard()` before `calculateTotal()` finishes would be a correctness bug, not just a performance one.

## 8. Quick Reference

```
for (x of items) { await f(x) }         → sequential, ~sum of all durations
await Promise.all(items.map(f))          → parallel, ~duration of the slowest one
independent steps → parallelize (Promise.all)
dependent steps (B needs A's result)      → must stay sequential
```
