The Codex / JavaScript / Event Loop

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.
// 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 elsewhere

The 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 after

The 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

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.