setTimeout(fn, 0) looks as if it should run “right now,” yet the next console line appears first. The timer is not broken. 0 is a delay used when scheduling the callback for a later task, not an instruction to interrupt the code currently running.
Why does the next line run before setTimeout(0)?
Run this example as written:
console.log('A');
setTimeout(() => console.log('D'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('B');A
B
C
DA and B belong to the current synchronous job. setTimeout registers callback D and returns, allowing B to print first. Changing 0 to 1 or 10 does not allow D to interrupt the current code.
The current code prints A and B. The Promise callback runs as a microtask, followed by the timer callback in a later task. A zero timer delay does not mean immediate execution within the current job.
Why does Promise.then run before the timer?
Here, Promise.resolve() is already fulfilled, but its .then() callback C still does not run at the call site. After the current code reaches B, the queued microtask runs before the timer's task, so C precedes D.
| Phase | Work | Console |
|---|---|---|
| Current code | Print, schedule timer, schedule Promise callback, print | A, B |
| Microtask | .then() on the fulfilled Promise | C |
| Later timer task | setTimeout callback | D |
Calling both callbacks “asynchronous” is too coarse to explain this order. Trace current code → microtask → timer task for this example. The MDN JavaScript execution model provides more detail on tasks and microtasks.
Is 0 ms a signal that earlier work is complete?
No. A zero-delay timer only schedules its callback; it does not know whether an earlier save or network request finished. Code that assumes “the save will be done on the next turn” can fail intermittently. If the work returns a Promise that represents completion, wait for that Promise.
async function saveAndNotify(save) {
await save();
console.log('Save complete');
}This message is justified only if save() returns a Promise that actually represents completion. If save() starts a separate request and returns nothing, await has no completion signal to wait for. Check the called function's contract.
Zero milliseconds is not an exact execution time either; busy work or host scheduling may delay the callback. A → B → C → D describes this small example, not a universal ordering guarantee for every asynchronous API or elapsed time.
Key takeaways
setTimeout(fn, 0) schedules a timer callback rather than running it immediately. In this example, synchronous A and B finish, then Promise microtask C, then timer task D. To check whether a job finished, await the Promise that represents it rather than using a timer.

