Skip to content

/glossary/nodejs/event-loop

What is the event loop in Node.js?

verified onenvironmentlocal · linux 6.18.44-fc-v236 x64 · GNU bash 5.2.21 · v22.23.4updated
tool
Node.js
versions
Node.js 20 – 26
os
any

The event loop is the mechanism that lets Node.js run JavaScript on a single thread and still handle many I/O operations at once. It takes finished operations (a timer that expired, a file read that completed, a request that arrived) and runs their callbacks one at a time. If the loop has nothing left to wait for, the process exits. The description below follows the official guide The Node.js Event Loop.

Phases

Each pass through the loop visits phases in a fixed order, and every phase has a first-in-first-out queue of callbacks:

PhaseWhat runs there
timersCallbacks from setTimeout() and setInterval() whose time has passed
pending callbacksSome system callbacks deferred to the next iteration, such as certain TCP errors
idle, prepareUsed internally only
pollRetrieves new I/O events and runs their callbacks; Node.js can block here while it waits
checksetImmediate() callbacks
close callbacksSome close events, for example socket.destroy()

Between phases, Node.js also runs process.nextTick() callbacks and resolved promise callbacks (the microtask queue) before moving on. According to the guide, starting with libuv 1.45.0 (Node.js 20), timers run after the poll phase in each iteration; earlier versions ran them before polling.

Example

bash
cat > loop.js <<'EOF'
const fs = require('node:fs');
fs.readFile(__filename, () => {
  setTimeout(() => console.log('timeout'), 0);
  setImmediate(() => console.log('immediate'));
});
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
EOF
node loop.js

Output

sync
nextTick
promise
immediate
timeout

The synchronous line prints first. Then nextTick and the promise callback run before the loop continues. Inside the I/O callback, setImmediate is guaranteed to run before a zero-delay timeout, because the loop moves from poll straight to check.

setTimeout, setImmediate and nextTick

  • process.nextTick() runs right after the current operation, before the loop continues. Recursive use can starve I/O.
  • setImmediate() runs in the check phase of the current iteration, after poll.
  • setTimeout(fn, ms) runs in the timers phase once at least ms have passed; it is a threshold, not an exact time.

Practical consequences

  1. Never block the loop. A long synchronous computation delays every other callback, timers included. Split the work or use a worker thread.
  2. Timers are late, not early. A 100 ms timer fires after 100 ms or more, depending on what else is running.
  3. Memory problems look like freezes. When the process is near its heap limit, garbage collection pauses the loop; see increase the memory limit.
  4. The process ends when the loop is empty. The exit status is described in Node.js exit codes.

FAQ

Is Node.js single-threaded?

Your JavaScript runs on one thread by default, but Node.js hands I/O to the operating system kernel and libuv, which can use other threads internally. The event loop is what brings the results back to your single JavaScript thread as callbacks.

What is the difference between setImmediate and setTimeout 0?

setImmediate runs in the check phase right after poll. A zero-delay setTimeout runs in the timers phase once its threshold has passed. From the main module the order can vary; from inside an I/O callback setImmediate always runs first.

Why does a long loop freeze my Node.js server?

Callbacks run one at a time. While your code occupies the thread, the loop cannot reach the poll phase, so no new connections or timers are handled. Split heavy work into chunks, or move it to a worker thread or another process.

Sources