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 anAggregateError. 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 realPromise.race([])does this)allSettled([])→ resolves with[]any([])→ rejects withAggregateError
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
| Combinator | Resolves when | Rejects when |
|---|---|---|
all | All resolve | Any one rejects |
race | First to settle (resolve) | First to settle (reject) |
allSettled | All settle (never rejects) | Never |
any | Any one resolves | All 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
ito place results in the correct position (preserves order) - Short-circuit logic —
allrejects immediately on first rejection;anyresolves 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 promisesfinallypass-through — understanding thatfinallydoesn't transform values, just observes settlement
Complexity
| Time | Space | |
|---|---|---|
| all | O(N) | O(N) for results |
| race | O(1) — first wins | O(1) |
| allSettled | O(N) | O(N) for results |
| any | O(N) worst case | O(N) for errors |
Interview Tips
- Implement
allfirst — it's the most commonly asked. Thenrace, thenallSettled, thenany. - Nail the empty array behavior — interviewers specifically test this:
all([])resolves with[],any([])rejects withAggregateError,race([])stays pending. - Use
Promise.resolve(p)on every input — this normalizes non-promise values without you writing a separate check. - Preserve order in
allwith index assignment —results[i] = value, notresults.push(value). Show you understand that promises might resolve out of order.