A practice prompt we wrote. No company or candidate report names it, so it carries no company tag.

How to answer

Say the core idea early: calling fetch starts the request, and the promise is only a handle on its result. So the cap must control when fetch is called; building every promise first has already lost.

  1. Pin the contract. mapLimit(items, limit, fn, { signal, timeoutMs }), where fn(item, index, signal) gets an AbortSignal, returns one outcome per input, in input order, shaped like PromiseSettledResult. Ask whether a failure cancels the rest; for a , collect.
  2. Rule out the near misses out loud. Promise.all(items.map(fn)) has no cap. Fixed chunks are capped but slow: each waits for its slowest request. In production you’d use p-map with concurrency; name it, then write the pool.
  3. Write the worker pattern. Start Math.min(limit, items.length) async workers. Each loops: take const i = next++, await fn inside a try, store the outcome at out[i]. Then await Promise.all(workers). The counter needs no lock, because nothing is awaited between reading next and incrementing it. The try catches a synchronous throw as well as a rejection, so no worker dies.
  4. Cover the edges. Reject a limit below one or a non-integer, and return [] for no input. Pass each call AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]), so a hung call cannot hold a slot forever and the caller can stop the run; workers check signal.aborted before taking the next index.
  5. Test with a fake that measures. It counts requests in flight, records the peak, and returns deferreds the test resolves in a chosen order, so a failure reproduces. Assert the peak never exceeds the limit and reaches it when there are at least limit items, outputs keep input order, and failures sit at their own indexes.

Then say what the cap does not do: it bounds concurrency, not rate. A per-second limit needs a too.

The capped fetch drill runs this with hidden tests on the cap, the order and the failures.

GlossaryBackfillReprocessing historical data through a pipeline, ideally with the same idempotent code as the daily run.More on BackfillGlossaryToken bucketA rate-limiting algorithm that refills tokens at a fixed rate and allows bursts up to a capacity.More on Token bucket

Follow-ups

What the interviewer may ask next, once your first answer is on the table.

  • The partner limits requests per second, not requests in flight. What changes in your code?
  • One request never settles. What happens to your pool, and how do you bound it?
  • The caller wants everything stopped on the first authentication error. How do you cancel the requests still running?
  • The inputs arrive as a stream you cannot hold in memory. What changes?

Where answers go wrong

  • Building items.map(fetchOne) and handing the array to a limiter, when every request started the moment map ran.
  • Running fixed batches through Promise.all, so one slow request holds up the rest of its batch and the cap is rarely reached.
  • Letting a rejection escape a worker’s loop, so Promise.all(workers) rejects on the first failure, the caller loses every result, and the surviving workers keep sending requests nobody reads.

Answer this in two minutes

Write the answer you would say out loud. The clock starts with your first word.

Two minutes

Model answer

“The idea everything hangs on: calling fetch starts the request, and the promise is only a handle on its result. So Promise.all(urls.map(fetch)) has no cap, and neither does handing an array of promises to a limiter, because the requests started when map ran.