The Event Loop Demystified

Breaking down the event loop — the mechanism at the heart of JavaScript's concurrency model — to understand what actually happens when your code runs.

Sanket Jawali
Sanket Jawali @SanketJawali

JavaScript is single-threaded. That statement confuses people because they see async/await, setTimeout, and fetch requests all happening “in parallel.” The key to understanding this is the event loop.

One Thread, Many Tasks

JavaScript has exactly one call stack. It runs one thing at a time. Period.

But the runtime environment — your browser or Node.js — has more than just the JS engine. It has:

  • Web APIs / C++ bindings (timers, network, file I/O)
  • A task queue (callbacks waiting to run)
  • A microtask queue (promises, queueMicrotask)
  • The event loop itself (the coordinator)
console.log("start");

setTimeout(() => console.log("timeout"), 0);

Promise.resolve().then(() => console.log("microtask"));

console.log("end");

Output:

start
end
microtask
timeout

This isn’t random. It’s deterministic, and understanding why this order happens is understanding the event loop.

The Loop

The algorithm, simplified:

  1. Execute everything on the call stack until it’s empty
  2. Drain the microtask queue completely
  3. Pick one task from the task queue, execute it
  4. Repeat

Microtasks always run before the next task. This is why promise callbacks fire before setTimeout callbacks, even with a 0ms delay.

Why This Matters

You Can Block the Loop

// This freezes your entire UI for ~5 seconds
const start = Date.now();
while (Date.now() - start < 5000) {
  // spinning
}

There’s no “background thread” to save you. If your synchronous code takes too long, everything waits — rendering, user input, network callbacks, all of it.

Microtask Starvation

Because microtasks drain completely before the next task, you can accidentally starve the task queue:

function recurse() {
  Promise.resolve().then(recurse);
}
recurse(); // This never yields to the task queue

This is a subtle but real footgun. Each .then adds a microtask, and the loop will keep draining microtasks forever.

The Mental Model

Think of it like a restaurant with one chef:

  • Call stack = the dish currently being prepared
  • Microtask queue = urgent fixes to the current dish (“add more salt”)
  • Task queue = the next order ticket
  • Event loop = the chef checking what to do next

The chef finishes the current dish, handles all the urgent fixes, then picks up the next order. Never two orders at once.


The event loop isn’t magic. It’s a simple, deterministic loop. But it’s the foundation everything else — async/await, React rendering, Node.js servers — is built on top of. Once you see it clearly, asynchronous JavaScript stops being confusing.