Chapter 6 of 17
~11 minThe Event Loop
The event loop is the mechanism that lets single-threaded Node.js handle many concurrent operations without blocking. Knowing its phases, and how microtasks fit between them, explains ordering questions that trip up almost everyone at first.
Why an Event Loop at All
JavaScript in Node.js runs on a single thread, but real applications constantly wait on things that take time - disk reads, network responses, timers. Rather than blocking that one thread on each of these, Node.js hands the operation off (to the OS or to libuv's thread pool) and registers a callback to run once it completes. The event loop is the process that continually checks whether any of those callbacks are ready, and runs them in a well-defined order.
The Phases of the Loop
Each pass through the event loop moves through a fixed set of phases, each with its own callback queue: timers (due `setTimeout`/`setInterval` callbacks), pending callbacks (certain system-level callbacks), poll (retrieving new I/O events and running most I/O callbacks), check (`setImmediate` callbacks), and close callbacks (e.g. a socket's `'close'` event). After each phase - and after every single callback - Node.js drains any pending microtasks before continuing.
const fs = require('fs'); console.log('start'); setTimeout(() => console.log('timeout'), 0); setImmediate(() => console.log('immediate')); fs.readFile(__filename, () => { console.log('file read callback'); setTimeout(() => console.log('timeout inside I/O'), 0); setImmediate(() => console.log('immediate inside I/O')); }); console.log('end'); // Inside an I/O callback, setImmediate reliably runs // before a 0ms setTimeout, because the poll phase // hands off directly into the check phase.
Microtasks vs Macrotasks
Promise callbacks (`.then`/`.catch`/`.finally`) and `queueMicrotask()` go into the microtask queue, which Node.js fully drains after the currently executing operation finishes and before moving to the next event-loop phase. Timers, I/O callbacks, and `setImmediate` are macrotasks, each tied to a specific phase of the loop. This is why a resolved Promise's `.then()` always runs before a `setTimeout(fn, 0)` scheduled around the same time - the microtask queue is emptied first.
console.log('1: sync'); setTimeout(() => console.log('4: timeout (macrotask)'), 0); Promise.resolve().then(() => console.log('3: promise (microtask)')); console.log('2: sync'); // Output order: 1, 2, 3, 4
process.nextTick vs setImmediate
`process.nextTick()` is not technically part of the event-loop phases at all - its queue is drained immediately after the current operation completes, even before Promise microtasks, and before the loop proceeds to its next phase. `setImmediate()`, despite the name, does not run instantly; it runs in the check phase, specifically after the poll phase's I/O callbacks for that iteration.
A practical rule of thumb: use `setImmediate()` when you want something to run after I/O callbacks in this same iteration of the loop, and reserve `process.nextTick()` for rare cases where something absolutely must run before the loop advances at all - overusing `nextTick()` recursively can starve I/O entirely, since the loop never gets to move on while the nextTick queue keeps refilling.
Coding challenge
Predict, then verify by running, the output order of a script that logs a synchronous message, schedules a setTimeout(fn, 0), schedules a process.nextTick(fn), and resolves a Promise with a .then(fn), all before a final synchronous log.
// Write your solution here
Answer every question below to mark this chapter as complete.
Quiz Yourself
Score: 0/51. Why does a resolved Promise's `.then()` callback run before a `setTimeout(fn, 0)` callback scheduled at the same time?
2. Which callback queue is checked first: process.nextTick or the Promise microtask queue?
3. What is a realistic risk of using process.nextTick() recursively (each call scheduling another)?
4. Inside an I/O callback (e.g. inside fs.readFile's callback), why does setImmediate reliably fire before a 0ms setTimeout scheduled at the same point?
5. What are the two broad phase categories macrotasks like timers, I/O callbacks, and setImmediate belong to, versus microtasks?