▶
All 15

The cheatsheet

Everything the interactive cases teach, on one page. Every rule links to the case where you can watch it happen.

Browser and Node are different loops — they get separate sections below. Last reviewed 4 August 2026.

Browser

One iteration of the event loop:

1 Run one task

sync code, a timer callback, an event handler

2 Drain ALL microtasks

including ones queued during the drain

3 Possibly render

rAF → style → layout → paint — not every iteration

4 Select the next task

from one of several task queues

setTimeout / setInterval Watch →

Goes to · Task queue (timer task source)

Runs · After the current task and every queued microtask; delay is a minimum

“The delay is an exact schedule”

Promise.then / .catch / .finally Watch →

Goes to · Microtask queue

Runs · At the microtask checkpoint after the current task; queue drains completely

“Registered earlier = runs earlier, even across queues”

A chained .then() Watch →

Goes to · Microtask queue — but only when the previous link settles

Runs · One extra trip through the queue per link

“A chain runs to completion as one unit”

Code after await Watch →

Goes to · Microtask queue, as a continuation, when the awaited promise settles

Runs · FIFO with all other microtasks — no special priority

“await blocks the thread / defers the whole function”

queueMicrotask Watch →

Goes to · Microtask queue — the exact queue promise reactions use

Runs · At the checkpoint in the CURRENT turn, FIFO with promise callbacks, before any task or paint

“It is a faster setTimeout(0)”

Goes to · Browser network layer; resolves its promise on response HEADERS

Runs · Continuation queues as a microtask when headers arrive; body is a second async stage

“await fetch() waits for the whole body · a 404 rejects”

requestAnimationFrame Watch →

Goes to · Registered for the next rendering opportunity

Runs · During the rendering step, after all microtasks, just before paint

“It fires every 16ms like a timer”

Node.js

Phases (timers → pending → poll → check → close). The two queues below are drained after EVERY callback returns — not just at phase boundaries:

1 Run ONE callback

e.g. one expired timer callback

2 Drain the nextTick queue

completely — Node-only, highest priority

3 Drain microtasks

promise callbacks, queueMicrotask

4 Next callback, or next phase

even a timer in the SAME phase waits for both drains

process.nextTick Watch →

Goes to · nextTick queue (Node-only, above microtasks)

Runs · Drained completely after EVERY callback returns, BEFORE promise microtasks — in CommonJS

“It is just another microtask”

Promise.then (Node) Watch →

Goes to · Microtask queue

Runs · After the nextTick queue, before the loop advances a phase

“Node ordering matches the browser exactly”

setImmediate Watch →

Goes to · Check queue — runs in the check phase, right after poll

Runs · Same loop iteration when scheduled from an I/O callback — before any fresh 0ms timer; at top level its order vs setTimeout(0) is a startup race

“"Immediate" means before everything else”

setTimeout(fn, 0) (Node) Watch →

Goes to · Timers phase, silently clamped to 1ms

Runs · The NEXT timers phase — a full lap away when called from an I/O callback

“0ms means it wins every race”

Top-level ESM code Watch →

Goes to · Evaluated as a module job — inside a microtask

Runs · Already inside a checkpoint, so pending promises finish before nextTick is revisited

“.cjs and .mjs print the same order”

The nextTick-before-promises ordering holds for CommonJS. Top-level ESM code already runs inside a microtask, which flips it — same file, different order.

Seven rules that settle most questions

  1. Synchronous code runs to completion. Nothing queued can interrupt it — not an expired timer, not a settled promise.
  2. A timer delay is a floor, never a schedule. "0ms" means "eligible as soon as the stack is empty", and a busy thread stretches every delay.
  3. After the stack empties, the entire microtask queue drains before the next task — including microtasks queued during the drain. This is also how microtask chains starve rendering.
  4. Registration order only decides ties WITHIN one queue. Across queues, queue type wins.
  5. Each link of a promise chain costs one trip through the queue. Independent chains interleave one link at a time.
  6. await suspends the function, never the thread. The rest of the function becomes an ordinary microtask when the awaited promise settles — no special priority.
  7. await fetch() unblocks on response headers; reading the body is a second async stage. HTTP error statuses resolve — only network failures reject.

Terminology: the HTML spec defines tasks and microtasks; "macrotask" is community shorthand for an ordinary task. Browsers keep several task queues, not one.

Sources: HTML Standard — Event loop processing model · Node.js — The event loop, timers, and process.nextTick() · MDN — The event loop

Every ordering on this sheet is enforced by the same validation that runs the interactive cases — see methodology. Found an error? The site is wrong before you are; check the case page and its sources first, then tell us.