FrontendAtlas
Interview Questions
Home>JavaScript interview questions> JavaScript coding challenges >Promise.any (First Fulfilled)

Promise.any (First Fulfilled)

intermediate
By FrontendAtlas Editorial · Updated Jul 14, 2026
Implement promiseAny(iterable) for synchronous iterables of values and promises. Resolve with the first fulfillment, ignore rejections until every iterated input rejects, and reject empty or all-rejected input with a native AggregateError whose errors preserve iteration order. Sparse array holes are iterated as undefined and therefore fulfill.

Arguments

  • iterable: Iterable<T | PromiseLike<T>> — A synchronous iterable of values or promise-like inputs, consumed in iteration order.

Returns

Promise<T> — Resolves to the first fulfilled value, or rejects with AggregateError when all reject.
Examples
promiseAny([
  Promise.reject(new Error('A')),
  Promise.resolve('ok'),
  Promise.resolve('later')
]).then(v => {
  // v === 'ok'
});

promiseAny([Promise.reject('x'), Promise.reject('y')])
  .catch(err => {
    // err.name === 'AggregateError'
    // err.errors === ['x', 'y']
  });

Solution

Overview

Consume the synchronous iterable with for...of, assign each entry an iteration index, and normalize it with Promise.resolve. Use a sentinel remaining count so empty input rejects without relying on array length and sparse arrays behave like native array iteration. Store rejection reasons by iteration index so completion timing cannot reorder AggregateError.errors.

1

Approach: Count rejections and aggregate reasons

Algorithm:

1) Return a base Promise and initialize errors, nextIndex, and remaining = 1 as an iteration sentinel.

2) Iterate with for...of so arrays, sparse arrays, Sets, and generators share one contract.

3) Reserve an index and increment remaining before attaching each Promise.resolve handler.

4) Resolve immediately on any fulfillment.

5) Store each rejection at its reserved index; decrement remaining and reject with native AggregateError when it reaches zero.

6) After iteration, remove the sentinel; zero now means the iterable was empty.

This mirrors Promise.any for synchronous iterable inputs and native Promise results, but it is not a generic Promise-constructor method and does not preserve Promise subclasses. Complexity: O(n) time and O(n) space for stored errors.

export default function promiseAny(iterable) {
  return new Promise((resolve, reject) => {
    const errors = [];
    let nextIndex = 0;
    let remaining = 1;

    try {
      for (const item of iterable) {
        const index = nextIndex++;
        remaining += 1;

        Promise.resolve(item).then(
          resolve,
          (reason) => {
            errors[index] = reason;
            remaining -= 1;
            if (remaining === 0) {
              reject(new AggregateError(errors, 'All promises were rejected'));
            }
          }
        );
      }
    } catch (error) {
      reject(error);
      return;
    }

    remaining -= 1;
    if (remaining === 0) {
      reject(new AggregateError(errors, 'All promises were rejected'));
    }
  });
}
export default function promiseAny<T>(
  iterable: Iterable<T | PromiseLike<T>>
): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const errors: unknown[] = [];
    let nextIndex = 0;
    let remaining = 1;

    try {
      for (const item of iterable) {
        const index = nextIndex++;
        remaining += 1;

        Promise.resolve(item).then(
          resolve,
          (reason) => {
            errors[index] = reason;
            remaining -= 1;
            if (remaining === 0) {
              reject(new AggregateError(errors, 'All promises were rejected'));
            }
          }
        );
      }
    } catch (error) {
      reject(error);
      return;
    }

    remaining -= 1;
    if (remaining === 0) {
      reject(new AggregateError(errors, 'All promises were rejected'));
    }
  });
}

Notes & Pitfalls

Pitfalls
  • Using Promise.race is incorrect because it rejects on the first rejection.
  • Using array length plus forEach leaves sparse-array holes unobserved and can keep the result pending forever.
  • Storing reasons in completion order violates AggregateError's required input-order contract.
Edge cases
  • new Array(1) iterates one undefined value and therefore fulfills with undefined.
  • Empty input and all-rejected input produce native AggregateError values.
  • Sets, generators, and other synchronous iterables are accepted; async-only iterables are outside this contract.
Techniques
  • Promise.resolve to normalize inputs.
  • Sentinel rejection counting independent of array length.
  • Native AggregateError with reasons stored by iteration index.

Resources

  • MDN – Promise.any()
  • MDN – AggregateError
  • ECMAScript – Promise.any

Similar questions

Create a Deferred Promise (For Async Tests)intermediateImplement Promise.allintermediateRun With a Performance Budget (Sync or Async)intermediate

Guides

Frontend interview preparation guideGuideFrontend coding interview questions and prep guideBlueprintJavaScript Problems That Actually Show UpBlueprintBuild Great UI in 60 MinutesBlueprint

Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.

Open frontend interview questionsBrowse JavaScript interview questionsOpen Essential 60Open Machine Coding HubOpen Frontend Coding ChallengesOpen System DesignOpen Interview Prep GuideOpen System Design BlueprintOpen Framework Prep PathsOpen Study PlansOpen JavaScript mastery study planOpen Company PrepOpen JavaScript Framework Prep Guide
↗Incidents hub
24 / 88