Prefer reading? The full explanation
How setImmediate Runs
setImmediate vs setTimeout(0) · Node.js · CommonJS
Before this
How Node Runs It — the phase model and the two intermediate queues.
After this you can explain
Why setImmediate always beats setTimeout(0) inside an I/O callback, and why the same duel in a main module is a genuine coin flip.
console.log('1: start');
fs.readFile('data.txt', () => {
console.log('3: I/O callback');
setTimeout(() => console.log('5: setTimeout'), 0);
setImmediate(() => console.log('4: setImmediate'));
});
console.log('2: script end'); Output: 1: start → 2: script end → 3: I/O callback → 4: setImmediate → 5: setTimeout
Two "run this next" APIs, two different phases
Node's event loop is a fixed cycle of phases: timers, pending callbacks, poll (where I/O completions are delivered), check, and close callbacks — and after every callback returns, Node drains the nextTick queue and then the microtask queue before touching the next callback. setTimeout(fn, 0) schedules its callback for the timers phase. setImmediate(fn) schedules its callback for the check phase. Neither means "now"; both mean "when the loop next passes my phase".
That single sentence replaces the folklore. Every ordering question about these two APIs reduces to: where is the loop standing when you schedule, and which of the two phases does it reach first from there?
Why the I/O callback makes it deterministic
The case above schedules both from inside an fs.readFile callback — which the poll phase delivers. From the poll phase, the loop's very next stop is the check phase: setImmediate runs in the same iteration. The freshly created 0ms timer, meanwhile, can only run when the loop wraps around to the next timers phase, a full lap away.
So inside any I/O callback, 4: setImmediate before 5: setTimeout is not probable — it is structural. The Node documentation states this guarantee explicitly, and this page's validator executes it on every build.
One honest footnote: in CI this case stubs the disk read with a timer, which delivers the callback in the timers phase instead of the poll phase. The guarantee is unaffected — from the timers phase, check still comes before the next timers phase — which is exactly why the stub is a legal shim: it swaps the mechanism and leaves the ordering to prove itself.
At top level, the same duel is a coin flip
Delete the readFile wrapper and run the two lines at the top of a module, and the output genuinely varies between runs. The reason is a detail most articles skip: Node clamps setTimeout(fn, 0) to a minimum of 1ms. When the loop begins its first iteration, it starts at the timers phase and asks: has 1ms elapsed since the timer was set?
On a fast startup the answer is no — the timers phase finds nothing, and the check phase runs setImmediate first. On a slower startup (busier machine, cold file cache, a debugger attached) the millisecond has passed, and the timer wins. Same file, both orders, neither a bug.
This is also a case study in what "verified" means on this site. A validator built on "run it once, assert the output" would be flaky here — or worse, would pass consistently while teaching an order that is not guaranteed. Worse still, the harness the other cases use starts every snippet from inside a callback, and from inside a callback this race cannot occur — an in-process check would happily report one fixed order forever. So this experiment is validated differently: the build launches it in thirty fresh Node processes, each a real module startup, and every observed output must be one of the two declared legal orders. The claim is not "the output is X"; the claim is "the output is never anything but X or Y" — which is the honest version.
And where does process.nextTick fit?
People compare process.nextTick and setImmediate because both names promise speed — but they are not two speeds of the same mechanism, they are different kinds of mechanism. setImmediate books a seat in a phase: its callback waits until the loop reaches check, after poll, this iteration. process.nextTick does not book a phase at all: its queue is drained the moment the current callback returns, before the event loop is allowed to continue anywhere — before other timers in the same phase, before I/O, before check.
So the honest ordering summary is: nextTick runs after the current operation, setImmediate runs after the current phase reaches check. The Node docs' own joke is that the names are swapped — process.nextTick fires more immediately than setImmediate does. The nextTick case on this site shows the further wrinkle that even that statement depends on the module system.
Practical rule: nextTick (or better, portable queueMicrotask) for "before anything else moves"; setImmediate for "after the I/O we already have is dealt with". They answer different questions, which is why replacing one with the other usually reorders a program rather than just re-timing it.
Which one should you actually use?
To defer work until after the current I/O batch: setImmediate. It is the only deferral primitive that yields to the poll phase between chunks, which is what keeps a busy server accepting connections (see the starvation example below). To defer within the current phase, before the loop moves at all: queueMicrotask — portable, standard, and drained before any phase transition.
setTimeout(fn, 0) in Node is almost always the wrong tool: it costs a 1ms clamp, lands in a phase you rarely mean, and its order against everything else depends on startup timing. If you find one in a Node codebase, it usually marks the spot where someone ported browser folklore.
Reading about it is one thing. Watching it run is another.
↑ Run it step by step