Coding problem · Medium · Async & Concurrency

Run Async Tasks with a Concurrency Limit

Medium Language practicals Most askedSeen at Netflix, Stripe, Vercel, Amazon

Implement mapWithConcurrency(items, limit, worker):

  • worker(item, index) returns a promise.
  • At most limit workers run at the same time.
  • Resolve with the results in input order (not completion order).
  • If any worker rejects, the returned promise rejects.

This is the core of tools like p-limit and of real tasks such as "call this API for 10,000 users without hammering it".

Hints

Hint 1

Start limit 'lanes'; each lane repeatedly takes the next unclaimed index until none remain.

Hint 2

Write results into results[index] so order is preserved regardless of completion order.

Write and run your solution

Loading the editor… (JavaScript is required to run code in your browser.)

Solution and explanation

Show the solution

Time O(n) scheduling · Space O(n) results

Start min(limit, n) async "lanes". Each lane claims the next index (safe without locks — JavaScript runs one callback at a time), awaits the worker and stores the result at that index. Promise.all over the lanes rejects as soon as any worker throws.

Compare with Promise.all(items.map(worker)), which starts everything at once, and with batching (chunk + Promise.all), which waits for the slowest item in each batch.

Reference solution

async function mapWithConcurrency(items, limit, worker) {
  const results = new Array(items.length);
  let next = 0;
  async function lane() {
    while (next < items.length) {
      const i = next++;
      results[i] = await worker(items[i], i);
    }
  }
  const lanes = Array.from({ length: Math.min(limit, items.length) }, lane);
  await Promise.all(lanes);
  return results;
}

Common mistakes

  • Using Promise.all on all items — no limit at all.
  • Fixed batches: one slow item stalls the whole batch.
  • Pushing results in completion order.

Follow-up questions

  • Keep going after failures and return Promise.allSettled-style results.
  • Add a per-task timeout with AbortController.