JavaScript event-loop series

Three sequential explainers that build a single mental model of the JS runtime. Independent of the cheatsheets. Read Parts 1 → 2 → 3 in order — each builds on the one before.

About the series

A three-part progression from "what gets logged in what order" to a full async / await migration playbook. The series was written to fix the three most common misconceptions about JavaScript's runtime:

  1. "setTimeout(fn, 0) runs immediately." (No — it sits behind every pending microtask and any earlier-queued timer.)
  2. "new Promise(...) is async." (The executor body is synchronous; only .then callbacks are deferred.)
  3. "A .then chain is one deferred unit." (It's N independent microtask queueings — two parallel chains interleave.)

Design principle

Every part pairs a runnable JS source file (src/js-q.js, src/js-q-2.js) with an explainer that traces the output character by character. Every trace was verified by running the code with Node 24 and matching against the predicted output. No hand-waving, no "trust me."

Mechanics

All three pages are fully responsive (1 / 2 / 3 columns by viewport), with a sticky table-of-contents bar on desktop and a hamburger drawer on mobile. They share styles.css + app.js (sibling files in this folder). Each part has forward / backward navigation in its header and footer so you can flip between them.

The progression at a glance

PART 1 Build the model Call stack, microtask queue, macrotask queue, the drain rule. Eight statements, one answer: A H D F G B C E.
PART 2 Break wrong intuitions Two more puzzles. Promise executors are sync. Chains interleave. Answer: 1 2 3 5 4 A1 B1 A2 B2 A3 B3.
PART 3 Map onto await How await fits, 9 migration traps, a 10-point checklist, plus Parts 1 & 2 rewritten in async/await.

Part 1 Event-loop explainer #1

Step-by-step trace of an eight-statement program (src/js-q.js) that mixes console.log, setTimeout, and Promise.then — the classic interview gotcha. Builds the mental model of call stack → microtask queue → macrotask queue from scratch.

prints:A H D F G B C E
  • Phase-by-phase trace with queue-state visualisation cards (stack / micro / macro / output)
  • Why microtasks always beat macrotasks, even when queued later
  • Why microtasks drain greedily — including microtasks queued mid-drain
  • Full 11-row trace table mapping every state transition from start to exit
  • Node-specific notes: process.nextTick, setImmediate, event-loop phases
Open Part 1 →

Part 2 Event-loop explainer #2

Two more counter-intuitive puzzles (src/js-q-2.js) targeting the two most common wrong mental models: "Promise is async magic" and "a .then chain runs atomically."

prints:1 2 3 5 4 A1 B1 A2 B2 A3 B3
  • The Promise executor body is synchronous — including code after resolve()
  • Two parallel .then chains interleave one step at a time, not chain-then-chain
  • Side-by-side "wrong guess" vs "actual output" comparisons
  • Connection to async/await: same interleave pattern under the hood
  • Brief tour of V8's "Faster async functions" optimisation (1 tick instead of 2 since V8 7.2)
Open Part 2 →

Part 3 await explainer & migration playbook

Where await fits in the event-loop picture, what it compiles down to, and 9 migration traps to watch for when moving a codebase from explicit .then chains to async/await.

  • Mental model: side-by-side "you write" vs "the engine effectively runs"
  • The 9 traps — lost parallelism, forEach+async, fire-and-forget rejections, return await, first-await shape change, argument eagerness, await on non-thenable, microtask flooding, top-level await
  • What you gain — readability, unified error handling, stack traces, debugger UX
  • A 10-item migration playbook checklist
  • Bonus: Parts 1 and 2 rewritten in async/await with verified output and a teaching note on what shifts when you "translate" — composition can change ordering even when individual units don't
Open Part 3 →

How it's built

node-ts-test/
├── cheatsheets/
│   ├── event-loop-index.html   ← you are here
│   ├── js-q-explainer.html     ← Part 1
│   ├── js-q-2-explainer.html   ← Part 2
│   ├── await-explainer.html    ← Part 3
│   ├── styles.css              ← shared Rose Pine, responsive grid, burger menu
│   └── app.js                  ← shared drawer toggle
└── src/
    ├── js-q.js                 ← Part 1 puzzle source — run with `node src/js-q.js`
    └── js-q-2.js               ← Part 2 puzzle source — run with `node src/js-q-2.js`
  • Each explainer is a single HTML file linking styles.css and app.js (same directory)
  • Only JS-specific syntax-token colours and per-page styles (trace tables, queue-state cards, phase pills) stay inline
  • Every trace was verified by running the corresponding .js file with Node 24 and diffing the actual output against the predicted output character-for-character
  • No framework, no bundler, no dependencies — three pages totalling ~155 KB

Further reading — event loop, promises & async

Canonical specs & runtime docs

MDN references

  • MDN — The event loop mdn High-level summary of the runtime model; good starter reading before diving into the WHATWG spec.
  • MDN — Promise mdn Full API reference: constructor, static methods (all, allSettled, race, any), instance methods.
  • MDN — async function mdn Reference for the async keyword and how it shapes function return types.
  • MDN — await operator mdn Reference for await semantics, including the spec-equivalent Promise.resolve(x).then(...) behaviour.

Deep dives by humans

Visualisers & talks

  • JS Visualizer 9000 jsv9000.app Step through any JS snippet and watch the call stack, microtask queue, and macrotask queue update in real time. Pair with Part 1 and Part 2 source files for an instant a-ha moment.
  • Loupe (latentflip) latentflip.com Philip Roberts' classic visualiser — the one used in his "What the heck is the event loop anyway?" talk. Slightly older (predates microtasks UI), but the call-stack animation is excellent.
  • Philip Roberts — What the heck is the event loop anyway? (JSConf EU 2014) youtube The talk that introduced thousands of devs to the call-stack-plus-queue model. Older than the microtask era but still the best 25-minute intro.

Credits

  • Theme: Rose Pine — palette by Emilia Dunfelt & contributors. Used in its "Main" variant with the standard role mapping (love, gold, rose, pine, foam, iris).
  • Fonts: system UI sans for prose; JetBrains Mono / Fira Code / SF Mono / Menlo for code blocks.
  • Validation: every page checked with Python's html.parser; every event-loop trace verified by running the corresponding .js file with Node 24 and comparing output character-for-character.
Looking for the standalone cheatsheets? They're in the cheatsheets index — TypeScript 6.0 and curl, both unrelated to this series.