Synchronous iteration reads values that already exist. Async iteration reads values that arrive over time, such as pages from an API or lines from a socket. for await...of awaits each value before the loop body runs, which is what lets a slow consumer pace a fast producer instead of buffering the whole stream.
Before you start
You should be comfortable with promises, for...of, and generator functions. This example uses an async generator and does not cover Node.js stream objects, cancellation tokens, or the exact scheduling of the event loop. Focus on who decides when the next value is requested.
Step-by-step walkthrough
Step 1: Make the source async iterable
An async generator function (async function*) returns an async iterable. Its Symbol.asyncIterator produces an object whose next() returns a promise of { value, done }. Any source that models “values over time” fits this shape: paginated HTTP results, database cursors, or a queue.
Step 2: Await each value in the loop
for await...of waits for each next() promise before running the body. If the promise rejects, the rejection is thrown into the loop and the loop stops unless you catch it. break, return or a thrown error calls the iterator’s return(), so finally blocks in the generator run and cleanup is not skipped.
Step 3: Let the consumer control the pace
Because the generator only advances when the loop asks for the next value, a consumer that awaits slow work applies backpressure: the producer is not asked for another item until the current one is handled. That guarantee disappears the moment you collect values with Promise.all or push them into an unbounded array.
Worked scenario
Run this with Node.js. Each value is delivered only when the consumer requests it.
async function* fromArray(items) {
for (const item of items) {
await Promise.resolve();
yield item;
}
}
const seen = [];
for await (const value of fromArray([1, 2, 3])) {
seen.push(value);
}
console.log(seen); // [1, 2, 3]Walk through the example
The loop requests one value, fromArray resumes up to the yield, and control returns to the body. The body finishes before the loop asks for the next item, so the generator stays suspended in between. Order is preserved even though the source is async, and the internal awaits do not change that: each next() resolves one value at a time.
Common mistake
Writing array.forEach(async (x) => { await work(x); }) does not wait: forEach ignores the returned promises and the surrounding function continues. For sequencing, use for await...of (or for...of with await). The mirror-image mistake is Promise.all(array.map(async ...)) over an unbounded source, which starts every task at once and removes backpressure entirely.
Verify the behavior
Assert that order is preserved and that the loop stops on break. Add a finally block inside the generator and confirm it runs after an early break. Make the generator throw and confirm the for await loop rejects so a caller can catch it. Time a deliberately slow body and check that the source is not drained ahead of the consumer.
Interview exercise
A teammate says the async loop “runs in parallel because each step is awaited.” Is that correct?
Answer and reasoning
No. for await...of processes values sequentially: the next next() is not requested until the current iteration’s body settles. Parallelism requires explicitly starting several operations and awaiting them together, for example with Promise.all, which is the opposite of backpressure and should be used only when the source is bounded.
Follow-up discussion
Does an async generator need return? Not for normal completion, but implementing finally (or return) makes cleanup deterministic when a consumer stops early. Can you mix sync and async iteration? No: for...of throws on an async iterable, and for await over a plain iterable resolves each value to itself, so be explicit about which protocol a source implements.
Continue learning
Compare this with JavaScript iterators and iterable objects and generator cleanup. For the full protocol, read for await…of and try the JavaScript interview questions.