The Event Loop, Visualized

An illustrated course · free to learn

See exactly how the JavaScript event loop runs your code.

Step through the event loop one animated figure at a time — explained simply, for developers, or straight from the spec. Then paste your own code and watch it run.

1console.log('start');
2
3setTimeout(() => {
4 console.log('timeout');
5}, 0);
6
7Promise.resolve().then(() => {
8 console.log('promise');
9});
10
11console.log('end');

The script starts executing top to bottom on the call stack. console.log("start") runs synchronously and prints right away.

Call stack

script

Web APIs — browser land

The event loop — is the call stack empty? drain the microtask queue first then take one callback.

Microtask queue — priority

Callback queue

Console

01start

Fig. — the classic: Promise vs setTimeout, running on a loop · step 1 of 9

One figure. Three voices.

The animation never changes — the explanation does. Switch anytime with the toggle above, or keys 1 · 2 · 3.

Explain simply

One chef, one kitchen, a VIP line. The whole loop as a story a curious kid can follow.

Developer

Call stack, task queue, microtasks — the practical model behind every "why is this async?" bug.

Under the hood

Task sources, microtask checkpoints, render opportunities — straight from the HTML spec.

The event loop, in words

What is the JavaScript event loop?

JavaScript runs on a single thread — it can do one thing at a time. The event loop is the runtime's scheduler: it executes the current script on the call stack, then drains the microtask queue (promise callbacks), then picks the next task (timers, events) from the task queue — over and over. Watching it run, tick by tick, is the fastest way to make the model stick.

Why does setTimeout(0) run after a promise?

Because zero milliseconds means "not before 0ms", not "now". When the stack empties, the runtime first drains every microtask — and each .then() callback is a microtask — before it takes even one timer callback from the task queue. So a resolved promise always beats setTimeout(fn, 0). Watch it happen →

What's the difference between tasks and microtasks?

Both are queued callbacks, but microtasks (promise reactions, queueMicrotask, the resume of an await) get a VIP lane: the queue drains completely after the current script, before any task — or the next render. Tasks (timeouts, events, I/O) wait their turn one at a time. Starve the loop with an endless stream of microtasks and timers never eat — see the starvation scene →

Can I visualize my own code?

Yes — paste any snippet using console.log, setTimeout, promises, queueMicrotask, or async/await into the playground and step through it on the same animated call stack, queues, and Web APIs — forward, backward, at any speed.

Inspired by Philip Roberts' Loupe · © 2026 · all rights reserved