Skip to main content

What is the event loop

The event loop is the mechanism inside the JavaScript engine that drives code execution, handles asynchronous operations and coordinates the call stack with the task queues. The event loop is exactly what makes JavaScript asynchronous and non blocking, even though the language itself is single threaded.

Theory

TL;DR

  • The event loop decides which task runs now and which one waits, so the main thread is never blocked.
  • An asynchronous callback enters the stack only when the stack is empty.
  • Microtasks (Promise.then, queueMicrotask) have higher priority than macrotasks (setTimeout, events, I/O).
  • After each macrotask the engine drains the entire microtask queue before taking the next macrotask.
  • If synchronous code never finishes, the event loop never reaches the queues at all.

Quick example

javascript
console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4');

Output:

javascript
1 4 3 2

Why:

  1. The synchronous code runs first (1, 4).
  2. The promise callback (.then) goes into the microtask queue.
  3. setTimeout goes into the macrotask queue.
  4. The event loop first runs every microtask (3), then picks up the next macrotask (2).

The main participants

To understand the event loop you need four structures:

  1. Call Stack - where the current code runs: functions and expressions.
  2. Task Queue / Callback Queue - the macrotask queue: timers, events, setTimeout, setInterval, I/O.
  3. Microtask Queue - the microtask queue: Promise.then(), queueMicrotask(), plus process.nextTick() in Node.js.
  4. Event Loop - the loop that watches everything: if the stack is empty it takes a job from the microtask queue; if that is empty it takes a job from the macrotask queue.

Schematically:

javascript
+--------------------+ | Call Stack | <- runs the current code +---------^----------+ | (empty?) v +--------------------+ | Microtask Queue | <- Promise, queueMicrotask +---------^----------+ | (empty?) v +--------------------+ | Task Queue | <- setTimeout, setInterval, I/O +--------------------+ Event loop checks the queues and moves jobs into the stack

How the event loop works, step by step

  1. JavaScript runs the synchronous code, top to bottom.
  2. When an asynchronous operation appears, it is handed to the external Web APIs (a timer or fetch, for example).
  3. When the operation completes, its callback lands in the matching task queue.
  4. When the call stack is empty, the event loop takes a job from a queue and pushes it onto the stack.
  5. After all microtasks have run, the event loop moves on to the next macrotask.

This repeats continuously, in an endless loop.

If the stack is never freed, the queues are never processed at all:

javascript
setTimeout(() => console.log('2'), 0); while (true) {} // infinite loop

The event loop will never reach the setTimeout, because the stack never empties. Asynchronous jobs run only when the stack is empty.

Microtasks vs macrotasks

Task typeExamplesWhen it runs
MicrotasksPromise.then, queueMicrotaskright after the current code and before the next macrotask
MacrotaskssetTimeout, setInterval, fetch, I/O, DOM eventsafter all microtasks

In other words, microtasks have the higher priority.

In the browser vs in Node.js

EnvironmentEvent loop specifics
BrowserWeb APIs provide timers, fetch, DOM events
Node.jshas its own phases: timers, I/O, check, close, nextTick and so on

But the idea is the same in both: one thread, asynchrony through the event loop.

Summary

ComponentPurpose
Call Stackruns the current code
Web APIscarry out asynchronous operations
Task Queueholds macrotask callbacks
Microtask Queueholds promises and the microtask jobs
Event Loopmoves ready jobs from the queues into the stack

Common mistakes

  • Expecting setTimeout(fn, 0) to run fn immediately. A delay of 0 only means "as soon as possible once the stack and all microtasks are done".
  • Thinking the event loop is part of the language. It belongs to the runtime, the browser or Node.js, not to the ECMAScript specification.
  • Getting the priorities wrong: a promise created after a timer still runs first, because microtasks come first.
  • Scheduling microtasks from inside microtasks forever: the microtask queue is drained completely, so such a loop freezes the page just like while (true).
  • Expecting setTimeout(fn, 100) to fire exactly after 100 ms. That is a minimum delay; the real time depends on how busy the stack is.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.