The Event Loop & Microtasks

Why promises always beat setTimeout(0) -- the single-threaded model, the call stack, and two queues.

Syntax// per loop turn: run one macrotask, then drain ALL microtasks

JavaScript 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, await continuations), 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, timeout

Example

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example Β· js
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.

Example Β· js
console.log("start");
setTimeout(() => console.log("macrotask"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("end");
// Output: start, end, microtask, macrotask

queueMicrotask for fine-grained scheduling

queueMicrotask schedules a callback in the microtask queue so it runs after current sync code but before any macrotasks.

Example Β· js
queueMicrotask(() => console.log("microtask via queueMicrotask"));
console.log("sync");

Discussion

  • Be the first to comment on this lesson.
The Event Loop & Microtasks β€” JavaScript | SoundsCode