Watch 10 video solutions for Parallel Execution of Promises for Individual Results Retrieval, a medium level problem. This walkthrough by NeetCode has 539,243 views views. Want to try solving it yourself? Practice on FleetCode or read the detailed text solution.
Given an array functions, return a promise promise. functions is an array of functions that return promises fnPromise. Each fnPromise can be resolved or rejected.
If fnPromise is resolved:
obj = { status: "fulfilled", value: resolved value}
If fnPromise is rejected:
obj = { status: "rejected", reason: reason of rejection (catched error message)}
The promise should resolve with an array of these objects obj. Each obj in the array should correspond to the promises in the original array function, maintaining the same order.
Try to implement it without using the built-in method Promise.allSettled().
Example 1:
Input: functions = [
() => new Promise(resolve => setTimeout(() => resolve(15), 100))
]
Output: {"t":100,"values":[{"status":"fulfilled","value":15}]}
Explanation:
const time = performance.now()
const promise = promiseAllSettled(functions);
promise.then(res => {
const out = {t: Math.floor(performance.now() - time), values: res}
console.log(out) // {"t":100,"values":[{"status":"fulfilled","value":15}]}
})
The returned promise resolves within 100 milliseconds. Since promise from the array functions is fulfilled, the resolved value of the returned promise is set to [{"status":"fulfilled","value":15}].
Example 2:
Input: functions = [
() => new Promise(resolve => setTimeout(() => resolve(20), 100)),
() => new Promise(resolve => setTimeout(() => resolve(15), 100))
]
Output:
{
"t":100,
"values": [
{"status":"fulfilled","value":20},
{"status":"fulfilled","value":15}
]
}
Explanation: The returned promise resolves within 100 milliseconds, because the resolution time is determined by the promise that takes the longest time to fulfill. Since promises from the array functions are fulfilled, the resolved value of the returned promise is set to [{"status":"fulfilled","value":20},{"status":"fulfilled","value":15}].
Example 3:
Input: functions = [
() => new Promise(resolve => setTimeout(() => resolve(30), 200)),
() => new Promise((resolve, reject) => setTimeout(() => reject("Error"), 100))
]
Output:
{
"t":200,
"values": [
{"status":"fulfilled","value":30},
{"status":"rejected","reason":"Error"}
]
}
Explanation: The returned promise resolves within 200 milliseconds, as its resolution time is determined by the promise that takes the longest time to fulfill. Since one promise from the array function is fulfilled and another is rejected, the resolved value of the returned promise is set to an array containing objects in the following order: [{"status":"fulfilled","value":30}, {"status":"rejected","reason":"Error"}]. Each object in the array corresponds to the promises in the original array function, maintaining the same order.
Constraints:
1 <= functions.length <= 10Problem Overview: You receive an array of functions where each function returns a promise. All functions should execute in parallel, and the final promise should resolve with an array of results in the same order as the input. If any promise rejects, the overall promise rejects immediately.
Approach 1: Sequential Await (O(n) time, O(n) space)
The straightforward idea is to iterate through the array and await each promise one by one. Store each resolved value in a results array and return it after the loop finishes. This approach preserves order naturally because each promise completes before the next begins. The downside is that execution becomes sequential instead of parallel, which defeats the purpose of concurrent asynchronous work. Time complexity is O(n) but total runtime depends on the sum of all promise durations rather than the maximum duration.
This method is useful for understanding the problem and verifying result ordering, but it wastes the concurrency that promises provide. In real systems where network calls or I/O operations dominate runtime, sequential execution significantly slows performance.
Approach 2: Parallel Execution with Completion Counter (O(n) time, O(n) space)
The correct strategy launches every async function immediately and tracks completion. Create a new Promise and iterate through the input array. For each index, invoke the function and attach a .then() handler. Store the resolved value in the results array at the same index to preserve ordering.
Maintain a counter that tracks how many promises have resolved. Each successful resolution increments the counter. Once the counter equals the number of functions, resolve the outer promise with the results array. If any promise rejects, call reject immediately so the outer promise fails early.
This design ensures all promises run concurrently while still guaranteeing ordered results. The runtime complexity is O(n) because each promise is processed once. Space complexity is also O(n) for the results array and bookkeeping.
This pattern mirrors how Promise.all behaves internally. It relies heavily on concepts from JavaScript Promises, asynchronous execution in async programming, and coordination patterns commonly used in concurrency.
Recommended for interviews: Interviewers expect the parallel execution approach. The sequential method demonstrates baseline understanding but fails to utilize concurrency. The counter-based parallel strategy shows that you understand how promises resolve asynchronously while preserving deterministic output ordering.
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Sequential Await | O(n) | O(n) | Simple baseline implementation when concurrency is not required |
| Parallel Execution with Counter | O(n) | O(n) | General case where promises should run concurrently and results must preserve order |
| Built-in Promise.all Pattern | O(n) | O(n) | Production code when native Promise utilities are allowed |