Software / Browser

The event loop

One thread. The stack runs to empty, then microtasks, then one task, then the browser may paint.

~[event_loop]
single-threaded cooperative concurrency non-blocking I/O

JavaScript Event Loop

JS is single-threaded with a host-managed concurrency model. I/O and timers are offloaded to Web APIs; callbacks are enqueued when ready. The loop's priority ordering is what makes async code predictable.

JavaScript Event Loop — execution priority diagram Execution order: ① synchronous code on call stack; ② drain all microtasks (Promise.then, queueMicrotask, MutationObserver); ③ dequeue one macrotask (setTimeout, I/O, MessageChannel); ④ drain all microtasks again; ⑤ requestAnimationFrame callbacks before paint; ⑥ browser paints. Then repeat. ① sync call stack ② microtasks drain ALL ③ one task task queue ④ microtasks drain ALL ⑤ rAF → paint Promise .then/.catch queueMicrotask() MutationObserver cbs async/await desugars to .then setTimeout / setInterval MessageChannel I/O · UI events one task per loop tick requestAnimationFrame ResizeObserver cbs IntersectionObserver cbs if browser decides to paint Key: microtasks starve the loop if infinite — no paint until the µ-queue empties. MessageChannel beats setTimeout(0) for high-priority tasks.
µ queue

Microtask Queue

Drained completely after every task and after the initial script. No rAF or paint occurs until the microtask queue is empty. Starvation risk: a microtask that enqueues itself loops forever, blocking all I/O and rendering.

  • Promise.then
  • queueMicrotask
  • MutationObserver
  • async/await
task queue

Task Queue (Macrotasks)

One task dequeued per loop iteration. Multiple distinct task sources exist (timer, I/O, UI events); the UA may interleave them. MessageChannel yields a macrotask without the 4 ms minimum clamp of setTimeout.

  • setTimeout
  • setInterval
  • MessageChannel
  • I/O callbacks
rAF

requestAnimationFrame

Fires immediately before the browser paints — ideal for all visual mutations. Batched per frame (~16 ms at 60 fps). Pauses when the tab is hidden. Use to batch DOM reads before writes and avoid layout thrashing.

  • requestAnimationFrame
  • requestIdleCallback
  • frame budget
scheduler

Scheduler API

scheduler.postTask() gives explicit priority control: user-blocking, user-visible, background. scheduler.yield() cooperatively yields a long task back to the event loop without relinquishing priority.

  • scheduler.postTask
  • scheduler.yield
  • TaskController
  • Long Tasks API