design·lab

Architecture

The Event Loop

A single thread stays responsive by never waiting on I/O — it hands async work off, then a loop delivers the results back in a strict order: sync code, then microtasks, then macrotasks.

✗ Problem

One thread. Block it and everything stops.

JavaScript (and many async runtimes) run your code on a single thread. A long synchronous task doesn't just delay itself — it freezes the UI, stalls every pending request, and blocks every other callback waiting to run.

function heavySync() {
  let x = 0;
  for (let i = 0; i < 5e9; i++) x += i;
  // ← one thread, nothing else runs
  return x;
}
button.onclick = heavySync;   // UI freezes
Call Stack
busy: heavySync()
✗ ✗ ✗
everything else
clicks · renders · requests
Naive blocking code freezes the whole app — no clicks, no re-renders, no requests served — until that one synchronous call returns.
✓ How it works

Stack, queues, and a loop that never blocks

The Call Stack runs synchronous code. Async work (timers, network, I/O) is handed to the runtime; when it completes, its callback is queued — never run immediately. Once the stack is empty, the Event Loop pulls the next callback. Microtasks (Promises) always drain completely before the next macrotask (timers, I/O).

Call Stack
runs sync code, LIFO
⇅ empty?
Event Loop
picks the next callback
↓ microtasks drain first
Microtask Queue
Promise .then/.catch
Task Queue
setTimeout, I/O, events
↑ fed by
Web APIs / I/O
timers, fetch, fs, sockets
✓ See it live

Step through the real execution order

Click Tick ▶ to advance the loop one step and watch items move between the Stack, the Microtask Queue, and the Task Queue.

console.log(1);
setTimeout(() => log(3));
Promise.resolve().then(() => log(2));
console.log('1b');
Call Stack
Microtask Q
Task Queue
Output

1 / 9
✓ Takeaway

Concurrency without threads

  • Single thread + non-blocking I/O = concurrency without the cost of threads.
  • Never block the loop — chunk heavy work or offload it to workers.
  • Order matters: all microtasks (Promises) drain before the next macrotask (timers, I/O).
  • Everywhere: this model underlies Node.js and every browser's JavaScript runtime.
🎯 Combines / relates: the loop is an Observer/callback dispatcher over queues; pairs with message queues and pub/sub for async at scale.