Skip to solution
hardDSA

How would you implement an async generator function to stream paginated API results, and how does a consumer use it with for await...of?

802 views
01

Understand the problem

Question presented to candidate: "Say you are calling a paginated API that returns a page of items plus a cursor for the next page. Write an async generator function that streams every item across every page to the caller one at a time, so the caller can use for await...of and start processing before every page has loaded. How do you prove this is actually lazy, one page at a time, and not secretly loading everything up front?"

What a strong answer should cover:

  • async function* combines a generator (pausable, resumable via yield) with an async function (can await inside), which is exactly the shape needed to await a network call between yields.
  • The generator holds a loop: await the current page, yield each of its items one at a time, then move to the next page using the cursor the API handed back, stopping when there is no next page.
  • for await...of on the consuming side automatically calls the async generator's .next() repeatedly, awaiting each result, and unwraps { value, done } for you — no manual iterator-protocol code needed.
  • Laziness is the key selling point over "fetch everything into an array first": only one page fetch is ever in flight at a time, and the NEXT page is not requested until the CURRENT page's items have all been consumed.
  • Breaking out of the for await...of loop early (a break, a return, or an uncaught error) triggers the generator's implicit cleanup and genuinely stops it from fetching any further pages.
  • This is testable, not just assertable — instrument the fetch function with timestamps or a counter and show the real call pattern rather than describing it from memory.

Clarifying questions expected:

  • "Should errors from a single page fetch stop the whole stream, or should the consumer be able to catch and continue?" — a real async generator propagates a thrown error out of the current for await iteration, ending the loop, unless the generator body itself catches it.
  • "Does the API give a full cursor, or just a page number, and can pages be fetched out of order?" — determines whether the loop can be parallelized at all, or whether it is fundamentally sequential.

Code / implementation expected: Yes — a full, runnable async generator plus a real, timestamped test proving only one page fetch is ever in flight, and that an early break genuinely stops further fetching.

async-generatorgeneratorsasync-iteration
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for JavaScript async/iteration interview questions. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. Every timing and count shown below is real, captured output from actually

Solution ready — 2 min read

Classified // press E to declassify

04

Run the code

JSAsync generator streaming a paginated API plus a real, timestamped test proving one-page-ahead laziness and early-break behavior (run directly)
Reference: the eager (broken) alternative this doc's pitfalls are based on — awaits every page up front, losing the streaming property (run directly to see the contrast)
const DB = [["a1", "a2"], ["b1", "b2"], ["c1", "c2"]];
let concurrentFetches = 0;
let maxConcurrent = 0;

async function fetchPage(pageIndex) {
  concurrentFetches++;
  maxConcurrent = Math.max(maxConcurrent, concurrentFetches);
  await new Promise((r) => setTimeout(r, 20));
  concurrentFetches--;
  return DB[pageIndex];
}

// BUG: this is a regular async function returning an array, not an async
// generator -- it awaits every page BEFORE returning anything, so the
// "streaming" claim is false: the whole dataset loads into memory up front.
async function eagerPaginate() {
  const allPages = await Promise.all(DB.map((_, i) => fetchPage(i)));
  return allPages.flat();
}

(async () => {
  const items = await eagerPaginate();
  console.log("eager result:", items);
  console.log("max CONCURRENT fetches in flight (all pages fired at once):", maxConcurrent);
  console.log("this defeats streaming: nothing is handed to the consumer until ALL pages have loaded");
})();
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 134 of 165 decoded in the JavaScript track. One more won't hurt.

Back to track