Skip to main content

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

  1. Call stack โ€” the function currently executing. One at a time, always.
  2. Microtask queue โ€” promise callbacks (.then, await continuations), queueMicrotask. Drained completely after every stack empties.
  3. 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.

Now practice

Event Loop โ€” PracticePredict and then implement: ordering quiz as code, a microtask vs macrotask probe, and an async task queue runner.2 challenges ยท ยท ~15 min