almessadi.
Back to Index

Node.js Scheduling Makes More Sense Once You Separate Microtasks from Phases_

The order between `process.nextTick`, promises, timers, and `setImmediate` is easier to reason about when you understand where each queue sits.

PublishedJuly 16, 2024
Reading Time4 min read

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