The Event Loop & Microtasks
Why promises always beat setTimeout(0) -- the single-threaded model, the call stack, and two queues.
// per loop turn: run one macrotask, then drain ALL microtasksJavaScript runs on one thread. Synchronous code executes on the call stack to completion. Anything async is scheduled onto a queue, and the event loop only pulls from a queue when the stack is empty.
Two queues, not one
There are two priorities:
- Microtasks -- resolved promises (
.then,awaitcontinuations),queueMicrotask. The whole microtask queue is drained after each task, before any rendering or timer. - Macrotasks --
setTimeout,setInterval, I/O events. One is taken per loop turn.
So a promise callback always runs before a setTimeout(0) scheduled at the same moment, because microtasks jump the queue.
console.log('sync');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
// order: sync, promise, timeoutExample
When to use it
- Understand why a setTimeout(fn, 0) callback runs after synchronous code even with zero delay.
- Predict the output order of a mix of Promises and setTimeout calls during a code review.
- Use queueMicrotask to schedule a callback at the end of the current task without yielding to I/O.
More examples
Call stack and event queue order
Synchronous code runs first; the setTimeout callback is queued as a macrotask and runs after the stack clears.
console.log("1 β sync");
setTimeout(() => console.log("3 β macrotask"), 0);
console.log("2 β sync");Microtasks run before macrotasks
Promise callbacks are microtasks and flush before the next macrotask (setTimeout), even with zero delay.
console.log("start");
setTimeout(() => console.log("macrotask"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("end");
// Output: start, end, microtask, macrotaskqueueMicrotask for fine-grained scheduling
queueMicrotask schedules a callback in the microtask queue so it runs after current sync code but before any macrotasks.
queueMicrotask(() => console.log("microtask via queueMicrotask"));
console.log("sync");
Discussion