JavaScript Event Loop Series: Building the Event Loop Mental Model from Experiments
I wrote this series because JavaScript has many "asynchronous" mechanisms (await, setTimeout, Promise, requestAnimationFrame) that look similar but behave very differently.
At first, I assumed they were interchangeable but that assumption quickly broke when I started debugging:
- Why
setTimeout(..., 0)doesn’t "run immediately" - Why
awaitpauses a function but doesn’t freeze the page - Why DOM updates sometimes don’t show up when you expect
- Why some "async" code still blocks rendering
These behaviours only make sense with the right mental model. This series builds that model by experimenting with small code snippets.
The core idea is simple:
JavaScript runs to completion inside a task, and nothing interrupts it.
From there, we layer in:
- macrotasks (what a task actually is),
- microtasks (why Promises run first),
async/await(pausing a function without pausing JavaScript),- rendering (why the screen doesn’t update mid-turn),
requestAnimationFrame(the missing pre-render scheduling layer), and finally,- what this means for real UI code.
Who this is for
This series is for you if you’ve ever felt that JavaScript async behavior is:
- predictable in practice, but unclear in theory
- "working" … until it suddenly doesn’t
- full of rules you remember, but don’t fully understand
You don’t need prior knowledge of the event loop. The goal is to build a mental model you can use to reason about behavior — not just memorize it.
How to read this series
Each article builds on the previous one. You can jump around, but the payoff is highest if you go in order.
- If you want the core mental model quickly: read Articles 1–3
- If you care about rendering and UI behavior: Articles 5–7 connect the model to what you see on screen
- If you just want answers: each article is self-contained, but the full model only emerges across the series
The articles
Here’s how the model unfolds:
1) Before the Event Loop: What Actually Runs JavaScript
Why doesn’t setTimeout interrupt your code? This article breaks the illusion: JavaScript runs synchronously, and async APIs don’t interrupt. Instead, they schedule.
Read it here.
2) Macrotasks: What a Task Actually Is
If nothing can interrupt JavaScript, when does anything else run? This article reframes tasks as entry points into execution, not chunks of work.
3) Microtasks: Why Promises Run First
Why do Promises always run before setTimeout? This article reveals microtasks as mandatory continuations that must run before JavaScript moves on.
4) async / await: Pausing Functions Without Pausing the World
Does await pause your program or just your function? This article shows how await actually works.
5) Rendering Is a Browser Decision, Not a JavaScript One
You updated the DOM. So why didn’t the screen change? This article explains why rendering is not triggered by JavaScript, but gated by it.
6) requestAnimationFrame: The Missing Scheduling Layer
If rendering only happens at certain moments, how do you run code at the right time? This article introduces requestAnimationFrame as the missing scheduling layer.
7) What the Event Loop Means for Real UI Code
Why do UIs freeze, skip updates, or feel laggy? This article connects the event loop to real-world UI behavior and shows how to work with the browser, not against it.
The mental model
This is the model everything in this series builds toward:
- The browser (runtime) starts a macrotask
- JavaScript runs synchronously until the call stack is empty
- The runtime drains all microtasks
- The browser runs any
requestAnimationFramecallbacks - Microtasks drain again (if any were queued during
requestAnimationFrame) - Only then can rendering happen
Continue the Conversation
If you want to discuss edge cases, counterexamples, or how this interacts with real applications, I’m always happy to chat.