Concept · The browser

The event loop

One thread, a task queue and a microtask queue — why a promise callback runs before a setTimeout.

3 minmicrotasktask queuecall stackmacrotask

Your JavaScript runs on one thread. It never runs two pieces of your code at once, which is why you don't need locks. Waiting work like timers, network requests and user input happens elsewhere in the browser. When it finishes, a callback is queued, and the event loop runs queued callbacks one at a time.

The loop, simplified

text
repeat:
  1. run one task (a script, a timer callback, an event handler) to completion
  2. run ALL microtasks (promise callbacks, queueMicrotask), including new ones they add
  3. if it's time, render (requestAnimationFrame callbacks, style, layout, paint)
  • Tasks (often called macrotasks) come from setTimeout, setInterval, DOM events and MessageChannel.
  • Microtasks come from .then/await continuations and queueMicrotask. The whole queue drains after every task, before the next task and before rendering.
js
console.log('1 script')
setTimeout(() => console.log('4 timeout (task)'), 0)
Promise.resolve().then(() => console.log('3 promise (microtask)'))
console.log('2 script')
// 1 script, 2 script, 3 promise (microtask), 4 timeout (task)

await is a microtask too. Everything after an await runs later, after the current task finishes.

What it explains

  • A long task freezes the page. While your function runs, nothing else does: no clicks, no rendering. Anything over about 50 ms is a "long task," and long tasks are what hurt INP. Split the work into chunks with await on a timer or scheduler.yield(), or move it to a Web Worker.
  • setTimeout(fn, 0) isn't immediate. It waits for the current task and all microtasks, and browsers clamp nested timers to at least 4 ms.
  • Microtasks can starve rendering. A promise chain that keeps scheduling more microtasks never lets the browser paint.
  • DOM changes don't paint until the task ends. Setting a style and then removing it in the same task never shows on screen. See how the browser renders.

Node.js has the same model with a few extra phases. Server languages with real threads trade this simplicity for data races you have to prevent yourself.

Learn it in depth