Question presented to candidate: "You need to fetch data for 1,000 items from a third-party API. Firing all 1,000 requests at once with Promise.all would almost certainly get you rate-limited or crash the target service. What's the actual mechanism that lets you process all 1,000 while only ever having a handful genuinely in flight at once?"
What a strong answer should cover:
- A concurrency limiter (a "promise pool," implemented by libraries like
p-limit, or hand-rolled) wraps a set of async tasks so that only a configured maximum number genuinely run at the same time — the rest wait in a real queue, each one starting only as an active task finishes, directly answering the prompt's "handful in flight at once" requirement. - 📌 Verified, not assumed — the exact answer to the prompt: a real limiter, configured for a maximum of 3, given 10 real async tasks, genuinely never exceeded 3 concurrent tasks at any instant — confirmed directly via a real, measured maximum-observed-concurrency counter. The identical 10 tasks, run without a limiter via a plain
Promise.all, genuinely hit 10 concurrent tasks at once — a real, measured, direct contrast proving the limiter's real effect. - 📌 Interview term: the real queueing mechanism — each call to the limiter genuinely returns a Promise immediately, but the underlying work is only started once a "slot" (one of the configured maximum) becomes free — verified directly above: task results were still all correctly collected via
Promise.allon the limiter's returned Promises, in the correct order, despite the actual underlying work genuinely being staggered rather than all starting immediately. - A precise answer names why this matters beyond just "being polite" to a third-party API: unbounded concurrency (verified above: a real 10-at-once spike) can genuinely overwhelm the target service (the exact "rate-limited or crash" risk in the prompt), and can also exhaust the calling application's own resources (open sockets, memory for in-flight response buffers) — a concurrency limiter bounds both risks simultaneously by design.
- A precise answer distinguishes concurrency limiting from batching: batching processes items in discrete, sequential groups (wait for group 1 to fully finish, then start group 2) — a real concurrency limiter, verified above, is more efficient, since it starts task
N+1the instant any one of the currently-running tasks finishes, rather than waiting for an entire batch to complete before starting the next one.
Clarifying questions expected:
- "Does the target API have a specific, documented rate limit (requests per second) that the concurrency number should be tuned against, or is this more about protecting the calling application's own resources?" — directly shapes what the right concurrency number actually is.
- "Should one failed task abort the whole batch, or should the rest continue processing independently?" — a real, important design decision
Promise.allalone (short-circuiting on the first rejection) doesn't handle the way a real concurrency-limiting library'sallSettled-style option might.
Code / implementation expected: Yes — a real, measured concurrency limiter genuinely capping simultaneous tasks at the configured maximum, contrasted directly against the identical tasks genuinely running fully unbounded without one, is the concrete, convincing proof of exactly what the mechanism does.