Engrain by ByteRush

JS.01.06 Mid-level· 11 minCore — asked often

The Node.js event loop: phases, setImmediate and process.nextTick

Node.js runs its event loop in phases. Learn the phase order, why setImmediate beats setTimeout inside I/O callbacks, and where nextTick fits.

Comes up in: JavaScript, Backend design

Learn#

Rohan’s Node.js server reads a file, then uses setTimeout(fn, 0) to “run it next”. In tests, a setImmediate callback keeps running first. He swaps them and the order changes again. Node.js does not have one queue. It has a loop with stages.

The loop comes from libuv, the C library under Node.js. Each turn passes through fixed phases, and each phase has its own queue.

In order: pending callbacks, idle/prepare (internal), poll (wait for I/O and run its callbacks), check (setImmediate), close callbacks, then timers. Since Node.js 20, timers run after poll, plus once before the loop starts.

So inside an I/O callback — which runs in poll — setImmediate always runs before a 0 ms timeout: check comes first.

Between every callback, Node.js empties two queues: first process.nextTick, then promise microtasks.

import { readFile } from "node:fs";
import { fileURLToPath } from "node:url";

readFile(fileURLToPath(import.meta.url), () => {
  setTimeout(() => console.log("4 timeout"), 0);
  setImmediate(() => console.log("3 immediate"));
  Promise.resolve().then(() => console.log("2 promise"));
  process.nextTick(() => console.log("1 nextTick"));
});
This example uses Node.js, so it runs in our checks rather than in your browser.

Output (verified)

1 nextTick
2 promise
3 immediate
4 timeout
One callback, four queuesStep 1 of 4

Call stack

  • readFile callback

Microtasks

  • nextTick: 1
  • promise: 2

Task queue

  • check: immediate
  • timers: timeout

Console

    empty

    Poll phase: the file has been read, so its callback runs. It schedules four things.

    Read the steps as text
    1. Poll phase: the file has been read, so its callback runs. It schedules four things.
    2. The callback returns. Node empties the nextTick queue first, then promise microtasks.
    3. After poll comes the check phase: setImmediate callbacks run.
    4. Then close callbacks, then timers. The 0 ms timeout finally runs.

    Predict#

    Predict first

    At the top level of your main file (not inside any callback), you call setTimeout(a, 0) and then setImmediate(b). Which runs first?

    Interview angle#

    Say it like this — about 30 seconds

    “Node.js uses libuv’s event loop, which runs in phases: pending callbacks, poll for I/O, check for setImmediate, close callbacks, and timers — since Node 20 timers run after poll. Inside an I/O callback, setImmediate always beats setTimeout zero. Between callbacks Node drains process.nextTick first, then promise microtasks. So nextTick is the most urgent, but it can starve I/O. Blocking work like file system and crypto calls runs on libuv’s thread pool.”

    Stays on your device. Nothing is uploaded.
    Follow-up List the phases of the Node.js event loop.
    Pending callbacks, idle/prepare, poll, check, close callbacks, timers. Timers also run once before the loop starts.
    Follow-up nextTick, setImmediate or setTimeout(fn, 0) — when would you use each?
    nextTick: rarely, to run before anything else (for example, emit an event after the caller attaches listeners). setImmediate: after the current I/O, before timers. setTimeout: when you need a delay.
    Follow-up Does nextTick always run before promise callbacks?
    Inside callbacks and in CommonJS, yes. At the top level of an ES module, promise callbacks run first, because the module itself is evaluated as part of a promise job.

    Practice#

    On your own

    Write order(kinds, where) that predicts Node.js ordering for the four kinds of callback. Return null when Node.js does not promise an order.

    Done when

    • Inside I/O: nextTick, then promises, then immediates, then timeouts.
    • At the top of an ES module: promises before nextTick.
    • Timeout and immediate together at module top level return null.
    Your solution
    Ctrl/⌘ + Enter

    Remember#

    Try to answer each card before you open it. Then add them to your reviews.

    Name the Node.js event loop phases in order.

    Pending callbacks, idle/prepare, poll, check, close callbacks, timers.

    Inside an I/O callback, which runs first: setImmediate or setTimeout(fn, 0)?

    setImmediate, always: check comes before timers.

    Inside a callback, which queue does Node.js drain first: nextTick or promises?

    nextTick, then promise microtasks.

    Which Node.js work runs on libuv's thread pool?

    Blocking work such as most fs calls, dns.lookup, some crypto and zlib.

    Go deeper

    Four layers. Stop at the one your interview needs; each ends with a quick check.

    The core ideaEvery round

    Node.js loops through phases, each with its own queue. I/O callbacks run in poll, setImmediate in check, timers in the timers phase. Between any two callbacks, nextTick callbacks run, then promise callbacks.

    Check yourself: Where do setImmediate callbacks run?

    In the check phase, right after poll.

    How it works underneathSenior rounds

    The Node.js guide lists the phases and the libuv 1.45.0 change (Node.js 20): timers now run after poll in each turn Source: Node.js: The event loop, timers, and process.nextTick(). process.nextTick is not a phase; its queue is drained after the current operation, whatever the phase.

    Check yourself: What changed for timers in Node.js 20?

    Timers run after the poll phase instead of before it, plus once before the loop starts.

    Edge cases and trapsWhere people slip

    At the top level of an ES module, the order flips:

    import { nextTick } from "node:process";
    Promise.resolve().then(() => console.log("promise"));
    nextTick(() => console.log("nextTick"));
    This example uses Node.js, so it runs in our checks rather than in your browser.

    Output (verified)

    promise
    nextTick

    The Node.js process docs describe this difference between ES modules and CommonJS Source: Node.js: process.nextTick(). Recursive nextTick calls can starve I/O, because the loop never reaches poll.

    Check yourself: Why do promises beat nextTick at the top of an ES module?

    The module is evaluated inside a promise job, so its microtasks drain before Node gets to the nextTick queue.

    In productionStaff-level answers

    libuv’s thread pool runs blocking work: most fs calls, dns.lookup, and CPU-heavy crypto and zlib calls. It has 4 threads by default, set by UV_THREADPOOL_SIZE before startup Source: libuv: Thread pool work scheduling. Network sockets do not use it. Your JavaScript still runs on one thread, so CPU work in a handler blocks every request — use worker threads for that.

    Check yourself: Four slow crypto.pbkdf2 calls are running. Why might a fifth wait?

    libuv's thread pool has 4 threads by default, so the fifth waits for a free thread.

    Finished the five steps? Mark it, and its cards join your reviews.

    Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

    Interview questions this answers

    • What is the difference between process.nextTick, setImmediate and setTimeout(fn, 0) in Node?
    • List the phases of the Node.js event loop.