Event Loop
How JavaScript handles asynchronous code — the call stack, heap, task queue, microtask queue, and why JavaScript is single-threaded but non-blocking.
The delay you give setTimeout is a minimum, not an appointment. When the time is up, the callback joins the queue, but it can only run once the call stack is empty. If your code is busy with a long synchronous job, every timer, click handler and network callback waits for it. A 100 ms timer firing after a full second is the result, and in a browser the page stays frozen for that whole second.
const start = Date.now();
setTimeout(() => {
console.log(`timer fired after ${Date.now() - start} ms (asked for 100)`);
}, 100);
// one second of synchronous work, e.g. processing a big array
while (Date.now() - start < 1000) {}
console.log("work done");
// work done
// timer fired after 1009 ms (asked for 100) // timer fired after 1009 ms (asked for 100)lt;- exact ms vary run to runWhy 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
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.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.