ai js event loop 2026-09-04

By Xah Lee. Date: .
work in progress

Explain JavaScript event loop

Explain JavaScript event loop

(ai answer)

The JavaScript event loop is the runtime mechanism that lets single-threaded JavaScript handle asynchronous work without blocking the main thread. It coordinates the call stack, host APIs (browser Web APIs or Node APIs), the macrotask queue, and the microtask queue. When the call stack is empty, the loop decides what runs next according to a strict priority order.

JavaScript itself has one call stack and can execute only one piece of code at a time. Long-running work (timers, network I/O, user events) is handed off to the host environment. When that work finishes, a callback is placed in a queue. The event loop is what moves those callbacks onto the stack at the right time.

The main pieces

A typical diagram looks like this:

Another common view that separates the two queues:

The loop’s algorithm

  1. Run the current synchronous code until the call stack is empty.
  2. Drain the entire microtask queue (including any microtasks that those microtasks themselves enqueue).
  3. (Browser) Optionally update rendering / paint.
  4. Dequeue and run one macrotask.
  5. Go back to step 2.

That asymmetry is the most important rule: all pending microtasks run before the next macrotask and before the browser paints.

Microtasks vs macrotasks

Microtask Macrotask
Examples Promise.then, queueMicrotask, await continuation, MutationObserver setTimeout, setInterval, I/O, UI events, setImmediate (Node)
When it runs After every task / when the stack empties One per event-loop turn
How many run The whole queue, even newly added ones Exactly one, then microtasks drain again
Rendering Browser will not paint until the queue is empty Paint can occur between macrotasks

A classic demonstration:

console.log('script start');

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

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

console.log('script end');

Output order:

  1. script start
  2. script end (synchronous)
  3. promise 1
  4. promise 2 (microtasks drain completely)
  5. setTimeout (the next macrotask)

setTimeout(..., 0) is not “run immediately”; it is a macrotask that waits until after all current microtasks.

Why this design exists

The model keeps the UI responsive: while a network request or timer is pending, the thread can still process clicks, animations, and other work. I/O is non-blocking by design. The trade-off is that a flood of microtasks (for example an infinite chain of Promise.then) can starve macrotasks and rendering — the page appears frozen even though the stack keeps emptying.

Node.js differences

Node’s event loop is organized into phases (timers → pending callbacks → poll → check → close, plus idle/prepare). process.nextTick has even higher priority than Promise microtasks. The same microtask-vs-macrotask priority still applies inside each phase, but the extra phases matter for setImmediate vs setTimeout and for I/O-heavy servers.

Once you internalize “empty the stack → drain every microtask → one macrotask,” the execution order of almost any mix of promises, async/await, timers, and events becomes predictable.