Promise Pool
Given an array of functions that each return a promise, and a number n, run them all but never more than n at the same time.
As soon as one finishes, the next should start — do not wait for a whole batch to complete. Resolve once every function has finished.
Examples
promisePool([f1, f2, f3], 2) // f1: 300ms, f2: 400ms, f3: 200msresolves after ~500msConstraints
- 1 <= functions.length <= 10
- 1 <= n <= 10
- Every function returns a promise
Notes
- Fixed batches are the common wrong answer: they idle until the slowest member of each batch finishes.
Hints
Editorial: Promise Pool
Pooling promises
Promise.all starts everything at once. When "everything" is fifty network requests, that is a stampede — the browser queues them anyway, the server sees a spike, and the slowest response still gates the result. A pool caps how many run concurrently.
Approach
The naive fix is batching: slice the list into groups of n and await each group. It is wrong in a way that is easy to miss — a group cannot start until the slowest member of the previous one finishes, so a fast task sits idle behind a slow one.
The fix is to think in workers rather than batches. Start n of them; each pulls the next unclaimed task, awaits it, and loops. A worker that finishes early immediately takes more work.
Implementation
async function promisePool(functions, n) { let next = 0; const worker = async () => { while (next < functions.length) { const index = next; next += 1; await functions[index](); } }; const size = Math.min(n, functions.length); await Promise.all(Array.from({ length: size }, worker)); }
Worth knowing
A shared next counter is safe without any locking because JavaScript is single-threaded: next += 1 cannot interleave. Math.min(n, functions.length) avoids spawning workers with nothing to do.
Time is the slowest possible packing of the tasks rather than their sum, and memory holds at most n in-flight promises however long the list is.