JavaScript is single-threaded — it runs one piece of code at a time. The event loop coordinates synchronous code, asynchronous callbacks, and I/O so the program stays responsive.

Call Stack

The call stack tracks function execution. When a function is called, it is pushed; when it returns, it is popped.

  function a() { b(); }
function b() { c(); }
function c() { console.log('done'); }
a();
// Stack: a → b → c → (pop c) → (pop b) → (pop a)
  

Web APIs / Node APIs

Operations like setTimeout, fetch, and file I/O are handled outside the main thread by the browser or Node.js. When complete, their callbacks are queued.

Task Queue (Macrotasks)

Callbacks from setTimeout, setInterval, I/O, and UI events go to the task queue (macrotask queue).

Microtask Queue

Promises (.then, .catch, .finally), queueMicrotask, and MutationObserver use the microtask queue, which has higher priority than the task queue.

How the Event Loop Works

  1. Run all synchronous code on the call stack until empty
  2. Run all microtasks until the microtask queue is empty
  3. Run one macrotask
  4. Repeat from step 2

Classic Example

  console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

// Output: 1, 4, 3, 2
  

Explanation:

  • 1 and 4 run synchronously
  • Promise callback (3) is a microtask — runs before macrotasks
  • setTimeout callback (2) is a macrotask — runs last

More Complex Example

  console.log('start');

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

Promise.resolve()
    .then(() => {
        console.log('promise 1');
        return Promise.resolve();
    })
    .then(() => console.log('promise 2'));

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

console.log('end');

// start → end → promise 1 → promise 2 → timeout 1 → timeout 2
  

async/await and the Event Loop

await pauses an async function and schedules the rest as microtasks:

  async function example() {
    console.log('A');
    await Promise.resolve();
    console.log('B'); // microtask
}
example();
console.log('C');

// A → C → B
  

Why This Matters

  • Explains why setTimeout(fn, 0) does not run immediately
  • Helps debug race conditions and ordering bugs
  • Clarifies why Promise callbacks run before setTimeout
  • Foundation for understanding performance and UI blocking

Blocking the Event Loop

Long synchronous tasks block everything — no rendering, no callbacks:

  // Bad: blocks for 5 seconds
let start = Date.now();
while (Date.now() - start < 5000) {}

// Better: break into chunks or use Web Workers
  

Understanding the event loop is key to writing efficient asynchronous JavaScript.