Implement Promise.all, race, any & allSettled

Four combinators, four different resolution strategies. Implement each from scratch — understanding exactly when each resolves, when it rejects, and what shape the result takes.

14 min read
JavaScript
Interview
Implementation
Promises

TABLE OF CONTENTS

Promise.all, race, allSettled, and any are four combinators that each settle differently. The interviewer wants all four — and tests whether you know which short-circuits on rejection, which on fulfillment, and what shape each result takes.


What are Promise Combinators?

Promise combinators are static methods on the Promise constructor that take an array of promises and return a single promise whose fate depends on how the input promises settle. Each combinator has a different rule for when it resolves and when it rejects.

  • Promise.all(promises) — resolves when all resolve, with an array of results in input order. Rejects immediately if any one rejects (short-circuit on first rejection). Use it for parallel independent work where all results are needed.
  • Promise.race(promises) — settles with the first promise to settle (resolve or reject). "First past the post" — winner takes all. Use it for timeouts and "fastest response wins" scenarios.
  • Promise.allSettled(promises) — resolves when all settle (resolve or reject), with an array of { status, value/reason } objects. Never rejects. Use it when you want results from every promise regardless of failure.
  • Promise.any(promises) — resolves when any one resolves (short-circuit on first success). Rejects only when all reject, with an AggregateError. Use it for "first successful response from multiple sources" scenarios.

The implementation of each follows the same pattern: return a new Promise, iterate inputs, attach .then/.catch to each, track counts, and resolve/reject when the combinator's rule is met. The key differences are in which settlement triggers what and what shape the result takes.

The interview tests whether you understand these four distinct settlement strategies, the edge cases for empty arrays (each combinator behaves differently), and the use of Promise.resolve() to normalize non-promise values. Implementing all four in one session is a common async marathon question.


The Problem

"Implement myPromiseAll(promises), myPromiseRace(promises), myPromiseAllSettled(promises), and myPromiseAny(promises) — all four from scratch."


Thought Process

Each combinator has one rule:

  • all — resolve when all resolve; reject as soon as ANY one rejects
  • race — settle as soon as the first promise settles (resolve or reject)
  • allSettled — resolve when all settle; never reject
  • any — resolve as soon as ANY one resolves; reject only when ALL reject

The pattern for each is the same: return a new Promise, iterate the input, attach .then/.catch to each, track counts.


Step 1 — myPromiseAll

Loading editor...


Step 2 — myPromiseRace

Loading editor...

Key: Race doesn't stop other promises — they keep running. The result of the first to settle is what the returned promise settles with.


Step 3 — myPromiseAllSettled

Loading editor...


Step 4 — myPromiseAny

Loading editor...


Step 5 — Edge Cases

Empty array:

  • all([]) → resolves with []
  • race([]) → stays pending forever (the real Promise.race([]) does this)
  • allSettled([]) → resolves with []
  • any([]) → rejects with AggregateError

Non-promise values: Promise.resolve(x) wraps non-promises, so "hello" is treated as Promise.resolve("hello").

Already-settled promises: Work fine — .then fires synchronously for resolved promises.


Comparison Table

CombinatorResolves whenRejects when
allAll resolveAny one rejects
raceFirst to settle (resolve)First to settle (reject)
allSettledAll settle (never rejects)Never
anyAny one resolvesAll reject

Full Solution

Loading editor...


Step 5 — Promise.finally

finally(onFinally) runs a callback when the promise settles — regardless of outcome — and passes through the original value or reason unchanged:

Loading editor...

The key insight: finally is not a transformation — it can't change the resolved value or rejection reason unless it throws. Wrapping onFinally() in Promise.resolve() means it works whether onFinally is sync or async.


What Interviewers Are Testing

  • Promise construction — creating a new Promise and resolving/rejecting from within
  • Index tracking — using index i to place results in the correct position (preserves order)
  • Short-circuit logicall rejects immediately on first rejection; any resolves immediately on first resolution
  • Edge case: empty input — each combinator handles empty arrays differently
  • Promise.resolve() wrapping — ensuring non-promise values are treated as resolved promises
  • finally pass-through — understanding that finally doesn't transform values, just observes settlement

Complexity

TimeSpace
allO(N)O(N) for results
raceO(1) — first winsO(1)
allSettledO(N)O(N) for results
anyO(N) worst caseO(N) for errors

Interview Tips

  • Implement all first — it's the most commonly asked. Then race, then allSettled, then any.
  • Nail the empty array behavior — interviewers specifically test this: all([]) resolves with [], any([]) rejects with AggregateError, race([]) stays pending.
  • Use Promise.resolve(p) on every input — this normalizes non-promise values without you writing a separate check.
  • Preserve order in all with index assignmentresults[i] = value, not results.push(value). Show you understand that promises might resolve out of order.

Related Questions


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI