NODE.JS GUIDE

Node.js Event Loop Explained with a Practical Example

By the HireReadyAI team · Last updated 5 October 2026 · 8 min read

The phases, where nextTick and promises fit, and a short example that trips up most candidates.

What the event loop is

Node.js runs your JavaScript on a single main thread. The event loop is what lets that one thread handle thousands of connections: slow work such as network and disk I/O is handed off to the operating system or libuv’s thread pool, and when it finishes, its callback is queued. The event loop keeps picking queued callbacks and running them, one at a time.

That is why one slow, synchronous function hurts everyone: while it runs, no other callback can.

The phases

Each turn of the loop moves through a fixed set of phases, and each phase has its own queue of callbacks:

PhaseWhat runs
timersruns expired setTimeout and setInterval callbacks
pending callbacksruns some system-level I/O callbacks deferred from the last loop
pollgets new I/O events and runs their callbacks; waits here if there is nothing else to do
checkruns setImmediate callbacks
close callbacksruns close handlers, such as socket.on('close')

Microtasks: nextTick and promises

Two queues sit outside the phases. After each callback finishes, Node empties the process.nextTick queue first, then the promise microtask queue (.then, await continuations). Only then does the loop move on.

So microtasks always run before the next timer or I/O callback, and nextTick runs before promises.

A practical example

Predict the output before reading on:

console.log('start');
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
console.log('end');

Output:

start
end
nextTick
promise
timeout     (order of these two
immediate    can vary, see below)

The two synchronous logs run first. Then the microtask queues drain: nextTick, then the promise. When this runs from the main module, the order of timeout and immediate depends on how quickly the loop starts, so it can change between runs. Inside an I/O callback, setImmediate always runs first, because the check phase comes straight after poll.

Common mistakes

  • Saying Node.js is fully single-threaded. Your JavaScript is, but I/O and some built-ins use libuv’s thread pool.
  • Assuming setTimeout(fn, 0) runs immediately. It waits at least until the timers phase.
  • Calling process.nextTick recursively, which starves I/O because the loop never moves on.
  • Running CPU-heavy work (large JSON parsing, crypto, image processing) on the main thread instead of worker threads.

Interview follow-up questions

  • Why can recursive nextTick calls freeze a server, while recursive setImmediate calls do not?
  • How would you move a CPU-heavy task off the main thread?
  • What runs in libuv’s thread pool, and how do you change its size?
  • How would you detect event loop lag in production?

All guides · HireReadyAI home