What does this script print, and why? console.log('A'); new Promise((resolve) => { console.log('B'); resolve(); console.log('C'); }).then(() => console.log('D')); console.log('E');
answer
- the constructor calls it immediately
- resolve is a call, not a return
- settled does not mean callback ran now
- the last letter is not D's neighbour
basics
~20 sIt prints A, B, C, E, D. The function passed to the Promise constructor runs synchronously, and calling resolve does not stop it or run the then callback — that callback is queued as a microtask and runs after the script finishes.
solid answer
~40 sThe output is `A`, `B`, `C`, `E`, `D`. The executor — the function you pass to `new Promise` — is invoked **synchronously by the constructor**, before `new Promise` even returns, so `B` prints right after `A`. Calling `resolve()` only changes the promise's state; it is an ordinary function call, not a `return`, so `C` still prints. Then `.then(...)` registers a reaction on a promise that is already fulfilled, which enqueues that reaction as a microtask rather than running it. The script continues and prints `E`. Once the script finishes and the stack empties, the microtask checkpoint runs the reaction and prints `D`. The mental split to hold: executor body is synchronous, reaction callbacks are asynchronous, always.
go deeper
Recall the two headline facts: the function passed to new Promise runs right away, and a then callback never runs on the spot. Being able to say the output in order is enough at this level.
Explain why each letter lands where it does, including that resolve() is a plain call that settles state and returns, and that a reaction on a settled promise is still queued as a microtask.
Bring in the consequences you have hit in real code: work after resolve() still executing, exceptions after settling vanishing, and promise wrappers that people mistake for offloading blocking work.
Own the API-design angle: the always-async rule exists so consumers face one control flow rather than two. Be ready to argue why a library that sometimes calls back synchronously is a defect, not an optimization.
## The output ``` A B C E D ``` Two separate misconceptions have to be cleared to get this right, and interviewers use this shape precisely because it separates them. ## The executor runs synchronously `new Promise(executor)` calls `executor` immediately, on the current call stack, before the constructor returns the promise object. There is no queueing and no deferral. That is why `B` prints between `A` and `E` — the executor body is just part of the top-level script's straight-line execution. This has a practical consequence beyond the puzzle: any expensive or blocking work you put in an executor body blocks the current turn exactly as it would anywhere else. Wrapping a synchronous computation in `new Promise` does not make it asynchronous. ```js new Promise((resolve) => { // still blocks the caller — a promise wrapper is not a thread const total = heavyLoop(); resolve(total); }); ``` ## resolve() is a function call, not a return `resolve` is an ordinary function the constructor handed you. Calling it transitions the promise from pending to fulfilled and records the value. It does **not**: - stop the executor — the rest of the body runs, so `C` prints; - run any reaction callbacks — none are registered yet at that instant; - do anything at all on a second call, since a promise settles once and later `resolve`/`reject` calls are ignored. A common bug follows from the first point: code after `resolve()` keeps running and can throw. Once a promise is settled, a later throw in the executor is swallowed rather than rejecting the promise, so the error disappears. Writing `return resolve(value);` is the usual defensive habit. ## The reaction is always asynchronous By the time `.then(() => console.log('D'))` executes, the promise is already fulfilled. It is tempting to think the callback can therefore run right away — the value exists, nothing is pending. The specification forbids it: a reaction is always delivered as a **promise job** on the microtask queue, never synchronously on the registering stack. That guarantee is deliberate. If callbacks sometimes ran synchronously and sometimes later, every caller of an API returning a promise would have to defend against both, and code could observe half-initialized state depending on the resolution timing. Promises trade a little latency for a uniform rule: your callback runs on a clean stack, after the current synchronous work is done. So `D` waits. `E` prints as part of the script. The script ends, the stack empties, the microtask checkpoint drains the queue, and `D` prints last. ## Tracing it as columns | now (synchronous) | microtasks | |---|---| | A | | | B (executor body) | | | C (after resolve) | | | E | D | Read the left column top to bottom, then the right one — that is the output. ## Variants an interviewer may bolt on - **Resolve later.** Move `resolve()` into a `setTimeout` and `D` moves after the timer callback, because the reaction can only be queued once the promise settles. - **Register `.then` twice.** Both reactions are queued at registration time, in registration order, and both run in the same drain. - **Add `setTimeout(() => console.log('T'), 0)` before the promise.** `T` prints after `D`: microtasks drain before the loop takes the timer task. - **Reject instead of resolve.** With `.then` alone and no rejection handler you get an unhandled rejection; the printed synchronous order is unchanged. ## What the interviewer is grading Not the five letters — the two sentences behind them: *the executor is synchronous* and *reactions are never synchronous*. A candidate who says both, then reads the trace off, has demonstrated the model. A candidate who guesses `A B C D E` has usually merged "the promise is already resolved" with "so the callback runs now", which is exactly the confusion that produces real bugs in initialization code.
- If the executor throws after calling resolve(), what happens to that error?It is swallowed. A throw in the executor rejects the promise only while it is still pending; once `resolve()` has settled it, the promise is immutable, so the later exception has nowhere to go and is silently discarded. That is why people write `return resolve(value);` — it prevents code after settling from running at all.
- Does wrapping a slow synchronous computation in new Promise make it non-blocking?No. The executor runs synchronously on the current stack, so the computation blocks the turn exactly as before; the only thing you gained is a promise-shaped result delivered a microtask later. Genuine offloading needs a different execution context, not a promise wrapper.
- Why does the specification refuse to run a .then callback synchronously on an already-fulfilled promise?For uniformity. If timing depended on whether the promise had settled yet, every consumer would face two different control flows and could observe partially initialized state. Always deferring to a microtask means a reaction runs on a clean stack, after the current synchronous work, in every case.
saying these in an interview costs you the question
- Says the executor body runs asynchronously or 'later'
- Thinks resolve() returns from the executor and skips the rest
- Claims .then runs immediately when the promise is already fulfilled
- Says a second resolve() call overwrites the first value
- Assumes wrapping sync work in new Promise makes it non-blocking