DEV Community

ValPetal Tech Labs
ValPetal Tech Labs

Posted on

Javascript Interview Question of the Day #18 [Talk::Overflow]

This post explains a quiz originally shared as a LinkedIn poll.


🔹 The Question

"use strict";
const promise = new Promise((resolve, reject) => {
    setTimeout(() => reject("Rejected"), 0);

    resolve("Resolved");

    setTimeout(() => resolve("Timeout"), 0);

    console.log("End");
});

promise
    .then(data => console.log("then:", data))
    .catch(err => console.log("catch:", err));
Enter fullscreen mode Exit fullscreen mode

Hint: Once a promise changes state, can anything else change it again? Pay attention to which calls run synchronously inside the executor versus what gets scheduled.

Follow me for JavaScript puzzles and weekly curations of developer talks & insights at Talk::Overflow: https://talkoverflow.substack.com/


🔹 Solution

Correct answer: A) End, then: Resolved

The output is:

End
then: Resolved
Enter fullscreen mode Exit fullscreen mode

🧠 How this works

This quiz tests one of the most fundamental — yet frequently misunderstood — rules of Promises: a promise can only be settled once. Once resolve() or reject() is called, the promise transitions from pending to either fulfilled or rejected, and that state is permanent. Any subsequent calls to resolve() or reject() are silently ignored.

Inside the Promise constructor, the executor function runs synchronously. The first setTimeout schedules a reject() as a macrotask (it doesn't run yet). Then resolve("Resolved") executes immediately and settles the promise as fulfilled. The second setTimeout schedules another resolve() as a macrotask (also doesn't run yet). Finally, console.log("End") prints.

When the macrotasks eventually fire, both the reject() and the second resolve() are no-ops — the promise is already settled.

This means only the .then() handler fires, and .catch() never executes.

🔍 Line-by-line explanation

  1. new Promise((resolve, reject) => { ... }) — The executor function runs synchronously the moment the Promise is constructed.

  2. setTimeout(() => reject("Rejected"), 0) — Schedules a callback on the macrotask queue. This does NOT run now. It will run after all synchronous code and microtasks complete.

  3. resolve("Resolved") — This runs synchronously and immediately settles the promise in the fulfilled state with the value "Resolved". This is the critical moment — the promise is now permanently settled.

  4. setTimeout(() => resolve("Timeout"), 0) — Schedules another macrotask. Like the first setTimeout, this doesn't run yet.

  5. console.log("End") — Runs synchronously, prints End. Note: the executor continues running even after resolve() is called — resolve() does not act like return.

  6. The executor finishes. The Promise constructor returns the (already fulfilled) promise to the variable promise.

  7. .then(data => console.log("then:", data)) — Since the promise is already fulfilled, this handler is queued as a microtask.

  8. .catch(err => console.log("catch:", err)) — This handler is attached but will never fire because the promise is fulfilled, not rejected.

  9. Synchronous code is done. The microtask queue is processed: the .then() handler runs and prints then: Resolved.

  10. Macrotask queue is processed:

    • First setTimeout fires: reject("Rejected")silently ignored, promise is already settled.
    • Second setTimeout fires: resolve("Timeout")silently ignored, promise is already settled.

🔹 Key Takeaways

  1. A promise can only be settled once. The first call to resolve() or reject() wins. All subsequent calls are silently ignored — no errors, no warnings.

  2. resolve() does not stop executor execution. Unlike return, calling resolve() inside a Promise executor does not prevent the remaining code from running. If you want to stop, use return resolve(value).

  3. Synchronous code in the executor runs before any scheduled callbacks. setTimeout(..., 0) schedules work for later (macrotask queue), while resolve() called directly runs immediately during executor execution.

  4. The executor runs synchronously. The function passed to new Promise() is invoked immediately and synchronously — it is not deferred or queued.

  5. When wrapping async operations in Promises, guard against double-settlement. Use a flag or return after the first resolve()/reject() call to make the intent explicit and prevent subtle bugs in multi-path async logic.

Top comments (0)