The Event LoopIntermediate9 min04 / 11

The Node.js Event Loop

How one thread runs thousands of things at once — the phases, the queues, and the exact order your callbacks fire.

Node runs your JavaScript on a single thread — yet it can juggle thousands of network connections without breaking a sweat. The trick is the event loop: instead of waiting for slow work (a timer, a file read, a network response), Node hands that work off, keeps running, and comes back to the result later via a callback.

Understanding the loop is the difference between guessing what order things run in and knowing. Use the interactive visualizer below to build a program and watch it run, one honest step at a time.

#One thread, never blocking

Your JavaScript runs on one call stack. If you block it with slow, synchronous work, everything stops — no other requests, no timers, nothing. So Node's whole design is about never blocking: slow operations (I/O, timers) are delegated to the system (via a C library called libuv) and their callbacks are queued to run later, when the stack is free.

Think of it like

Think of a barista

A good barista doesn't stand and watch one espresso pour before taking the next order. They start the pour, take three more orders, steam some milk, then come back when the shot is ready. One person (one thread), lots of work in flight — because nothing blocks.

#The phases of the loop

Each turn of the loop (a tick) moves through a fixed set of phases. Each phase has its own queue of callbacks to run:

one tick of the event loop

  timers        setTimeout / setInterval callbacks
  pending       some deferred system callbacks
  poll          retrieve I/O events; run I/O callbacks
  check         setImmediate callbacks
  close         'close' event callbacks

         repeat every tick 

The two you'll reason about most are timers (where setTimeout(cb, 0) callbacks land) and check (where setImmediate(cb) callbacks land).

#Microtasks jump the queue

Two special queues are drained between every phase (and after every callback), before the loop moves on:

  1. `process.nextTick` callbacks — highest priority, run first.
  2. Promise callbacks (.then / await continuations) — the microtask queue.

Because they drain before the next phase, a promise or nextTick always runs before a setTimeout that was scheduled at the same time.

Common mistake

`setTimeout(fn, 0)` is not immediate

A 0ms timeout doesn't run now — it runs in the timers phase of a future tick, after the current synchronous code finishes and after all microtasks drain. "0" just means "as soon as the loop can get to the timers phase."

guess the order
console.log('1: sync');

setTimeout(() => console.log('2: timeout'), 0);
setImmediate(() => console.log('3: immediate'));

Promise.resolve().then(() => console.log('4: promise'));
process.nextTick(() => console.log('5: nextTick'));

console.log('6: sync');

Read the output top to bottom: all synchronous code first (1, 6), then the microtasks with nextTick ahead of promises (5, 4), then the loop's phases — timers (2) and finally check (3). The visualizer below animates exactly this.

Quick check

Which callback runs FIRST after the synchronous code finishes?

Quick check

Why doesn't setTimeout(fn, 0) run immediately?

#Why it matters

This model is why Node is great at I/O-heavy work (servers, APIs, proxies) and why one blocking loop or a giant synchronous computation can freeze an entire server. Keep the loop free: do slow work asynchronously, and never block the thread.

Key takeaways

  • Node runs your JS on one thread and stays responsive by never blocking — slow work is delegated and its callback is queued.
  • Each tick runs phases in order: timers → pending → poll (I/O) → check (setImmediate) → close.
  • Microtasks drain between every phase: process.nextTick first, then Promise callbacks — so they beat setTimeout(0).
  • setTimeout(fn, 0) means 'as soon as the timers phase is reached', not 'immediately'.
  • Blocking the loop with heavy synchronous work stalls every other request — keep it free.
Try it yourself · The Node.js event loop — build & run it
Add nextTick, promises, timers & setImmediate, then watch the phases fire.
program.js · node
1console.log("start")
2setTimeout(() => console.log("timeout"), 0)
3setImmediate(() => console.log("immediate"))
4Promise.resolve().then(() => console.log("promise"))
5process.nextTick(() => console.log("nextTick"))
6console.log("end")
Call Stack
empty
nextTick queue
empty
Microtask queue (Promises)
empty
Timers phase (setTimeout)
empty
Check phase (setImmediate)
empty
Console
run script

The script runs top to bottom on the call stack.

step 1 / 24

Build your own program with the buttons above, then press Play to watch the exact order things run.

Practice challenges
Test yourself · earn XP
0/4
Predict the output#1

What does this Node program print?

predict-output
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
Predict the output#2

process.nextTick vs a Promise — which prints first?

predict-output
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
Fix the bug#3

A developer expects 'done' to print immediately. Why doesn't it?

fix-bug
setTimeout(() => console.log('done'), 0);
for (let i = 0; i < 1e9; i++) {}
console.log('loop finished');
Reorder the lines#4

Order these by when they run in a single Node program that schedules all of them at once.

1
synchronous console.log calls
2
setTimeout(…, 0) callbacks (timers phase)
3
Promise .then callbacks
4
process.nextTick callbacks
5
setImmediate callbacks (check phase)
Your turn
Practice exercise

Predict the console output of this program, then check with the visualizer:

``js console.log('A'); process.nextTick(() => console.log('B')); Promise.resolve().then(() => console.log('C')); setTimeout(() => console.log('D'), 0); console.log('E'); ``

Try it yourself — a starting point to build on:

starter.js
# Write your solution here