Explain the Node.js event loop and its phases.
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
The event loop cycles through phases — timers, pending callbacks, poll (I/O), check (setImmediate), close — processing callbacks per phase, draining microtasks (promises, process.nextTick) between each phase and after every macrotask.
Detailed explanation
Node runs JavaScript on one thread while libuv offloads expensive operations (file I/O, DNS, crypto) to the thread pool and OS; completion callbacks queue for the loop. Each iteration visits phases in order: timers run expired setTimeout/setInterval callbacks; pending callbacks run deferred system events (like some TCP errors); poll blocks waiting for new I/O when queues are empty, then executes I/O callbacks; check runs setImmediate callbacks; close handles 'close' events. Microtasks have strict priority: process.nextTick drains before promise microtasks, and both flush between *every* phase transition and individual timer callback — which is why a recursive nextTick can starve the loop entirely. Practical consequences: CPU-heavy work blocks everything (use worker_threads); ordering between setTimeout(fn, 0) and setImmediate depends on context — inside an I/O cycle setImmediate wins deterministically.
Example
// Inside an I/O callback:
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
});
// Logs: immediate → timeout (deterministic inside I/O phase)Interview tip
Name the four main phases in order (timers → poll → check → close), then volunteer the nextTick-starvation warning and the setImmediate-vs-setTimeout I/O quirk.
Common mistakes
- Saying Node is multi-threaded because of async operations.
- Not knowing microtasks drain between phases, not only at the end.