Skip to content
exercises
JavaScript

Promise pool of N

Limit concurrency without external deps.

IntermediateSelf

Implement a function that runs async tasks with limited concurrency — at most N tasks at once, refilling the pool as each task finishes.

The Problem

Promise.all runs everything at once. For a list of 1000 API calls, that’s 1000 simultaneous requests — you’ll either hit rate limits or overwhelm the server.

What you want: always have exactly N requests in-flight, starting the next one as soon as one finishes.

The Implementation

async function promisePool(tasks, concurrency) {
const results = [];
let index = 0;
async function worker() {
while (index < tasks.length) {
const taskIndex = index++;
results[taskIndex] = await tasks[taskIndex]();
}
}
const workers = Array.from({ length: concurrency }, () => worker());
await Promise.all(workers);
return results;
}

You spawn N workers. Each worker pulls the next task, runs it, then pulls another. When there are no more tasks, the worker exits. Promise.all on the workers waits until all are done.

Usage

const urls = ["https://api.example.com/1" /* ... */];
const tasks = urls.map((url) => () => fetch(url).then((r) => r.json()));
const results = await promisePool(tasks, 3); // 3 concurrent fetches

Why the Index Variable Works

The shared index variable and index++ inside the worker look like a race condition, but JavaScript is single-threaded. The index++ happens synchronously before the await. By the time the worker resumes after an await, another worker may have incremented index further — which is exactly what you want.

Why It Matters

Anywhere you process collections of async operations — image uploads, database writes, API calls — you need concurrency control. This pattern is 20 lines and has no dependencies.