What is Node.js and the event loop
Node.js is a JS runtime built on V8 that runs JS outside the browser, using libuv for async I/O. Its event loop has distinct phases — timers, pending callbacks, poll, check (setImmediate), close — and between every phase it drains microtasks (Promises) and process.nextTick (which runs even before Promises). Same single-threaded model, different phase structure than the browser.
Node.js is a JavaScript runtime that runs JS outside the browser — on the server, in CLIs, in tooling. It's built on V8 (the same engine as Chrome) plus libuv, a C library that provides the event loop and async I/O.
What Node adds over the browser
- No DOM /
window; instead:fs,http,net,process,Buffer, streams. - libuv thread pool for file I/O, DNS, crypto, compression (so blocking syscalls don't block the JS thread).
- CommonJS
require(and now ESM).
It's still single-threaded for your JS — concurrency comes from libuv + the event loop, same philosophy as the browser.
Node's event loop: phases
The browser has "macrotask queue + microtask queue." Node's loop is more structured — it runs through phases in order, each with its own callback queue:
┌───────────────────────────┐
┌─▶│ timers (setTimeout/Interval callbacks due)
│ ├───────────────────────────┤
│ │ pending callbacks (some system I/O callbacks)
│ ├───────────────────────────┤
│ │ poll (retrieve I/O events; execute I/O callbacks)
│ ├───────────────────────────┤
│ │ check (setImmediate callbacks)
│ ├───────────────────────────┤
└──│ close callbacks (e.g. socket 'close')
└───────────────────────────┘The two microtask-like queues
Between every phase (and between each callback), Node drains two queues, in this priority:
process.nextTickqueue — runs first, even before Promises. Highest priority.- Promise microtask queue —
.then/awaitcontinuations.
setTimeout(() => console.log("timeout"));
setImmediate(() => console.log("immediate"));
Promise.resolve().then(() => console.log("promise"));
process.nextTick(() => console.log("nextTick"));
console.log("sync");
// sync, nextTick, promise, timeout, immediate
// (sync first; then nextTick > promise; then timers phase, then check phase)setImmediate vs setTimeout(fn, 0)
setImmediate→ check phase, runs after I/O/poll.setTimeout(fn, 0)→ timers phase.- Inside an I/O callback,
setImmediatereliably runs beforesetTimeout(0); at the top level the order isn't guaranteed.
Browser vs Node — the summary
| Browser | Node | |
|---|---|---|
| Engine | V8 (Chrome) / others | V8 |
| Async backbone | Web APIs | libuv |
| Loop structure | task + microtask queues | phased loop + microtasks per phase |
| Extra priority queue | — | process.nextTick (before Promises) |
setImmediate | not standard | check phase |
Senior framing
The senior points: (1) Node is V8 + libuv, single-threaded JS with a thread pool for I/O; (2) its loop is phased, not a flat task queue; (3) process.nextTick outranks even Promises and can starve the loop if abused; (4) setImmediate vs setTimeout(0) ordering is deterministic inside I/O callbacks. That phase model is what distinguishes Node knowledge from generic event-loop knowledge.
Follow-up questions
- •What's the difference between process.nextTick and setImmediate?
- •How can process.nextTick starve the event loop?
- •What does libuv's thread pool handle?
- •How does Node's loop differ from the browser's?
Common mistakes
- •Assuming Node's event loop is identical to the browser's.
- •Thinking Node is multi-threaded for JS execution.
- •Overusing process.nextTick and starving I/O.
- •Assuming setImmediate always beats setTimeout(0).
Edge cases
- •setImmediate vs setTimeout(0) order is only guaranteed inside an I/O callback.
- •A long synchronous task or recursive nextTick blocks the whole server.
- •Worker threads exist for true CPU parallelism, separate from the loop.
Real-world examples
- •Express/Fastify servers, build tools, CLIs, streaming file processing.