Event Loop
How JavaScript handles asynchronous code — the call stack, heap, task queue, microtask queue, and why JavaScript is single-threaded but non-blocking.
What it is, in plain English: The event loop is the mechanism that allows JavaScript to be non-blocking despite running on a single thread. It continuously checks the call stack — if it's empty, it picks the next task from the queues and runs it. There are two queues: the microtask queue (Promises, queueMicrotask) which drains completely before each macrotask, and the macrotask queue (setTimeout, setInterval, I/O) which runs one task at a time. Understanding this explains every async timing puzzle.
Why JavaScript can do many things without threads
JavaScript runs on a single thread — it can only do one thing at a time. Yet it handles network requests, timers, user clicks, and file reads without freezing. How? The event loop.
The three pieces
Call Stack — where your code runs, one function at a time.
Web APIs / Node APIs — where async work actually happens (browser timer, network, file system) — outside the JS thread.
Queue — completed async work waits here to run on the stack.
Web APIs / Node APIs — where async work actually happens (browser timer, network, file system) — outside the JS thread.
Queue — completed async work waits here to run on the stack.
// This is what happens step by step:
console.log("A"); // 1. push to stack → run → pop
setTimeout(() => {
console.log("C"); // 4. much later: moved to queue → stack → run
}, 1000); // 2. handed to browser timer API (not JS)
console.log("B"); // 3. push to stack → run → pop
// Output: A B (1 second pause) C
// The stack never "waited" — it moved on to B while the timer ran elsewhereThe event loop in plain English
// Pseudocode of what the event loop does forever:
while (true) {
// 1. Run ALL synchronous code until the call stack is empty
// 2. Drain the ENTIRE microtask queue (Promises)
// 3. Run ONE macrotask (setTimeout callback, I/O callback, etc.)
// 4. Drain microtasks again
// 5. Maybe repaint the screen (browser only)
// 6. Repeat
}Two types of async tasks
// MICROTASKS — run BEFORE the next macrotask
// Sources: Promise .then/.catch, queueMicrotask(), async/await resumes
Promise.resolve().then(() => console.log("microtask"));
// MACROTASKS — run one at a time, after all microtasks drain
// Sources: setTimeout, setInterval, setImmediate (Node), I/O callbacks
setTimeout(() => console.log("macrotask"), 0);
console.log("sync");
// Output:
// sync ← call stack runs first
// microtask ← microtask queue drains before macrotask
// macrotask ← macrotask runs afterThe classic ordering quiz
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve()
.then(() => console.log("3"))
.then(() => console.log("4"));
console.log("5");
// Can you predict the order?
// Answer: 1 → 5 → 3 → 4 → 2
//
// Why: 1 and 5 run synchronously.
// Promise microtasks (3, 4) run before setTimeout macrotask (2).
// And .then() chains: 3 runs first, which queues 4, then 4 runs — all before 2.
Try It Yourself
Official Sources
- MDN Web Docs — Event loop
- WHATWG HTML Standard — Event Loop
- ECMAScript 2024 — Jobs and Host Hooks (§9.4)
- Jake Archibald — Tasks, microtasks, queues and schedules
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.