The Event Loop: One Thread, No Waiting
intermediate14 min readLesson 71 of 143
Call stack, task queue, microtask queue โ predict exactly what runs when, and why setTimeout(0) is never immediate.
JavaScript runs your code on one thread. It never blocks: slow work (timers, network, disk) is handed to the environment, and your callbacks are queued. The rules of that queue are the event loop.
The three queues that matter
- Call stack โ the function currently executing. One at a time, always.
- Microtask queue โ promise callbacks (
.then,awaitcontinuations),queueMicrotask. Drained completely after every stack empties. - Macrotask queue โ
setTimeout,setInterval, I/O callbacks, events. One macrotask per loop turn.
Per turn: run one macrotask โ drain all microtasks โ maybe render โ next macrotask. Microtasks always win.
Predicting the order
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
Prints 1, 4, 3, 2. Sync code first (1, 4), then microtasks (3), then
the timer macrotask (2). setTimeout(fn, 0) does not mean now โ it means
next macrotask, at the earliest.
Why it matters
- A long-running loop blocks everything: renders, clicks, timers. Chunk work
with
await new Promise(r => setTimeout(r))to yield. - Promise chains resolve before any timer callback โ ordering bugs come from forgetting which queue you are in.
- The UI can only update between tasks: await between chunks lets the browser paint.
MDN's Concurrency model and the event loop is the reference; this lesson is the mental model you will apply in every practice below.