The Node.js event loop feels mysterious until you stop treating it as one queue.
There are multiple queues and multiple priorities involved, and the most confusing part for many engineers is that microtasks are not the same thing as the main libuv phase loop.
A Useful Mental Model
This script is a good starting point:
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
Promise.resolve().then(() => console.log("promise"));
process.nextTick(() => console.log("nextTick"));
console.log("sync");
The important concepts are:
- synchronous code runs first
process.nextTick runs before other microtasks in Node
- promise callbacks run before the event loop proceeds to later phases
- timers and
setImmediate belong to different later phases
That is why process.nextTick is so powerful and so easy to misuse.
Why nextTick Can Hurt You
If you recursively schedule more nextTick work, you can starve I/O because Node keeps draining that queue before moving on.
That is why nextTick should be used sparingly.
If you need to yield so the event loop can keep serving the process, setImmediate is often the healthier tool.
The Engineering Value
This is not interview trivia.
It matters when you are:
- diagnosing latency spikes
- debugging strange callback ordering
- trying to avoid starving I/O
The event loop stops feeling magical once you think in queues and scheduling points instead of "async code happens later."
Further Reading