Skip to the solution
MediumDSA7 min read

How do you implement a custom Promise.all polyfill?

724 views

The problem

Question presented to candidate: "Promise.all takes an array of promises and resolves once every single one of them resolves, or rejects as soon as any one of them rejects. Can you implement a polyfill for it from scratch? Walk me through the edge cases: what happens with plain, non-promise values mixed into the array, an empty array, and how you keep the results in the right order when the promises do not settle in the order they were given."

What a strong answer should cover:

  • A new outer Promise wraps the whole operation, resolving only once every item has settled, and rejecting immediately on the first rejection.
  • Results must preserve INPUT order, not completion order β€” each result is written into a pre-sized results array at its own index, never pushed in arrival order.
  • Non-promise values (plain numbers, strings, objects) must be wrapped with Promise.resolve() so they flow through the identical counting logic as real promises.
  • An empty input array is a genuine edge case that needs an explicit check β€” without one, a naive implementation can silently never resolve at all.
  • A single rejection anywhere calls the outer reject immediately; sibling promises are not cancelled, they simply become irrelevant once the outer promise has already settled.
  • A countdown ("remaining") counter, not an array-length comparison, is what correctly handles the fact that settlement happens asynchronously and out of order.

Clarifying questions expected:

  • "Should the input accept any iterable, or specifically an array?" β€” determines whether to normalize with Array.from() up front.
  • "Do sibling promises need to be cancelled once the outer promise rejects, or is it fine if they keep running in the background?" β€” native Promise.all does not cancel them either, so this is worth naming explicitly rather than assuming.

Code / implementation expected: Yes β€” a full, runnable polyfill plus a real test proving results stay in input order regardless of completion order, and that rejection short-circuits correctly.

  • promises
  • polyfill
  • async

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 7 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

JavaScriptThe polyfill plus a real test proving order preservation, rejection short-circuiting, and native parity (run directly)
JavaScriptReference: the two real broken variants this doc's pitfalls are based on (run directly to see both bugs)
const delay = (ms, val) => new Promise((r) => setTimeout(() => r(val), ms));

function brokenPushVersion(iterable) {
  return new Promise((resolve, reject) => {
    const items = Array.from(iterable);
    const results = [];
    let remaining = items.length;
    if (remaining === 0) { resolve([]); return; }
    items.forEach((item) => {
      Promise.resolve(item).then((val) => {
        results.push(val); // BUG: completion order, not input order
        remaining -= 1;
        if (remaining === 0) resolve(results);
      }, reject);
    });
  });
}

function brokenNoEmptyCheck(iterable) {
  return new Promise((resolve, reject) => {
    const items = Array.from(iterable);
    const results = new Array(items.length);
    let remaining = items.length;
    items.forEach((item, i) => { // BUG: no check for items.length === 0
      Promise.resolve(item).then((val) => {
        results[i] = val;
        remaining -= 1;
        if (remaining === 0) resolve(results);
      }, reject);
    });
  });
}

(async () => {
  const pushResult = await brokenPushVersion([delay(30, "A-should-be-first"), delay(5, "B-should-be-second")]);
  console.log("push-based result, WRONG order:", JSON.stringify(pushResult));
  // REAL captured output: ["B-should-be-second","A-should-be-first"]

  const race = await Promise.race([
    brokenNoEmptyCheck([]).then(() => "resolved"),
    delay(200, "timeout-fired-instead"),
  ]);
  console.log("empty-array result within 200ms:", race);
  // REAL captured output: "timeout-fired-instead" -- it never resolved
})();

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 67 of 165 in JavaScript

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

What do flat and flatMap do?

Continue