intermediate
Node event loop
Explain event-loop phases, microtasks, nextTick, timers, immediates, I/O callbacks, and starvation risks.
Node runs JavaScript on a single main thread and schedules asynchronous work through libuv. The event loop is not one queue — it is a set of phases that run in order each turn:
┌───────────────────────────┐
│ timers │ setTimeout / setInterval callbacks
├───────────────────────────┤
│ pending callbacks │ I/O callbacks deferred from prior turn
├───────────────────────────┤
│ idle, prepare │ internal libuv housekeeping
├───────────────────────────┤
│ poll │ retrieve new I/O events; block if idle
├───────────────────────────┤
│ check │ setImmediate callbacks
├───────────────────────────┤
│ close callbacks │ e.g. socket.on('close')
└───────────────────────────┘
Between each phase and after the loop turn, microtasks run: `process.nextTick` first, then Promise jobs. That ordering explains why `nextTick` can starve I/O if abused.
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// nextTick → promise → (timeout or immediate depends on context)
On interviews: name the phases, explain microtask priority, and connect blocking the main thread to latency for all concurrent requests.
Common pitfalls: calling Node "multi-threaded" without mentioning the thread pool; assuming `setTimeout(fn, 0)` runs before I/O; recursive `nextTick` starving the poll phase.
The trade-off is balancing simplicity, performance, safety, and operability — name which axis you optimized and what cost you accepted.
Checklist:
- Draw phases and where timers vs immediates run.
- Explain microtasks vs macrotasks in Node.
- Tie main-thread blocking to service-wide latency.
- Mention libuv thread pool for some I/O, not all JS execution.