How process.nextTick, setTimeout, and setImmediate decide what runs first.

Figuring out why the console.log at the bottom of the file just printed first. Welcome to the Node.js Event Loop, where the rules are strictly enforced, but the interns somehow always have VIP access.
Every Node.js program begins the same way.
Usually with a quiet sense of confidence that the lines you wrote will execute exactly in the order you ✨ imagined ✨.
You write a few lines of code, and somewhere deep down you trust that the runtime will treat those instructions with the same straightforward logic.
Most of the time it does.
But sometimes the output appears in an order that feels a little uncanny, like the runtime nodded politely while you were speaking and then quietly rearranged the conversation behind your back.
Not maliciously.
Not even incorrectly.
Just… differently than your intuition expected.
Underneath your program sits a quiet little municipality called Event Loop , and inside that “ city ” a handful of workers are doing their best to coordinate timing, queues, and execution order without stepping on each other’s toes or collapsing the entire system under pressure.
They have roles.
They have boundaries.
They have priorities that sometimes feel strangely political.
Occasionally those priorities make perfectly reasonable code look _ **_cursed** .
The script we are about to examine looks harmless enough at first glance.
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
setImmediate(() => {
console.log('Immediate');
});
process.nextTick(() => {
console.log('nextTick');
});
console.log('End');
And the output that eventually arrives is short, and deceptively revealing.
Start
End
nextTick
Timeout
Immediate
Nothing crashed.
Nothing threw an exception.
But the ordering exposes how the event loop negotiates authority between its workers , and the story becomes clearer once we listen to each of them.
The Synchronous Code: The Foreman on the Factory Floor
At the beginning of execution there is only the call stack ,
which behaves less like a scheduler and more like a seasoned foreman making sure the current job gets finished before tomorrow’s tasks.
He has a simple rule that he follows with stubborn consistency:
If the instruction is in front of him right now, he executes it immediately and moves on to next without worrying about anything scheduled for later.
So the first instruction arrives.
console.log('Start');
The foreman reads it, nods once, and prints Start to the console with the quiet satisfaction of someone who appreciates predictable work.
Then the script begins registering asynchronous tasks.
Three of them, in fact.
- setTimeout
- setImmediate
- process.nextTick
The foreman recognizes these workers immediately ,
but they do not belong to his department.
They are future commitments.
Promises about work that should happen later once the current stack of synchronous instructions has been completed.
So he forwards those requests to their respective queues and continues walking through the file.
Eventually he reaches the final line.
console.log('End');
And once again he performs the instruction without hesitation.
Printing End to the console,
before finally setting his clipboard down and stepping away from the stack.
Only after the foreman leaves the floor does the rest of city begin speaking.
process.nextTick: The Overprivileged Intern
The first character to seize that moment is process.nextTick.
Who technically does not belong to any official event-loop phase but somehow always manages to speak before anyone else in the room.
NextTick is the intern who somehow has direct access to management, which means he has developed the slightly dangerous habit of interrupting the entire scheduling system whenever he feels something deserves attention.
As soon as the call stack clears, NextTick quietly raises his hand and says,
“Just a quick update before we move forward.”
The scheduler sighs, because it knows the rules.
NextTick callbacks must run before event loop proceeds into other phases , which means the system pauses briefly to let the intern finish his thought.
process.nextTick(() => {
console.log('nextTick');
});
And so the console receives the next line.
nextTick
No waiting.
No timers.
No poll phase.
NextTick always goes first, not because the system particularly loves him,
but because somewhere deep in Node.js design history someone granted him priority access to the queue.
Power like that has consequences.
The Timers Phase: setTimeout Tries to Be Patient
Now that the intern has delivered his message and returned to his desk,
the event loop continues moving through its regular phases until it reaches the department responsible for timers.
This is where setTimeout works.
Timeout is a careful worker who spends most of his day watching clocks and trying very hard not to wake the system earlier than promised.
When developers write code like this:
setTimeout(() => {
console.log('Timeout');
}, 0);
The intention often feels immediate , as though the callback should run right away because the delay was set to zero milliseconds.
Timeout and The Origami Software Engineer gently shakes their head.
“Zero milliseconds means not before the next timers phase ,
not literally right now”
So he sits in the timers queue and waits for the event loop to circle back around to his department, trusting that the scheduler will eventually give him the floor once higher-priority work has been completed.
Eventually that moment arrives.
The timers phase opens.
Timeout stands up.
And the console receives the next line.
Timeout
He was not late.
He was simply respecting the scheduling contract.
setImmediate: The Check-Phase Veteran
After the timers phase finishes its work, the event loop continues its rotation until it reaches a quieter department known as the check phase , where an experienced worker named setImmediate patiently waits for his turn to contribute.
Immediate has been in the system for a long time, and he has learned not to rush things because his role only begins after the poll phase has completed its work and the loop is preparing to close the current iteration.
He understands the rhythm of the city.
He understands that I/O might delay him.
He understands that sometimes the timers department speaks first.
In this particular script there is no active I/O work ,
which means the loop reaches the check phase fairly quickly and Immediate finally receives the floor.
setImmediate(() => {
console.log('Immediate');
});
The console prints the final message.
Immediate
Immediate is satisfied that the meeting has concluded in proper order.
The Quiet Hierarchy
When all the characters have spoken,
The output reads like a short transcript of the city’s internal politics.
Start
End
nextTick
Timeout
Immediate
First the synchronous foreman ,
who only trusts work he can execute immediately .
Then the overprivileged intern ,
who interrupts the meeting before anyone else gets a chance to speak.
Then the timers department ,
patiently honoring scheduled delays .
Finally the check-phase veteran ,
who closes the loop once the system has completed its normal cycle.
At first the ordering can feel strange, almost like the runtime rearranged your intentions behind your back.
But the runtime is not lying.
It is simply following its internal hierarchy.
The Event Loop Is Not Magic, It’s Social Structure
Many developers approach the event loop expecting a perfectly mechanical system, something that behaves like a deterministic conveyor belt.
The reality feels much more human.
Different queues hold different responsibilities.
Different workers carry different authority.
And occasionally someone like process.nextTick gains enough influence that they can interrupt the flow whenever they feel something should run immediately.
This is why code like the following can quietly destabilize the entire system.
function loop() {
process.nextTick(loop);
}
loop();
NextTick keeps raising his hand.
Again.
Then again.
Then again.
The scheduler keeps honoring the request because the rules say it must drain the entire nextTick queue before moving forward.
Eventually the event loop stops progressing altogether.
Not because the system crashed.
Because someone kept interrupting the meeting.
Learning the Rhythm
Once you spend enough time watching the event loop ,
the behavior stops feeling random and starts revealing a rhythm that governs how work flows through the runtime.
The order tends to unfold like this:
- The call stack finishes executing synchronous work.
- process.nextTick callbacks rush forward with privileged urgency.
- Promise microtasks quietly update state before the next loop iteration.
- Timers honor scheduled delays.
- Poll-phase I/O moves external data through the system.
- setImmediate closes the iteration during the check phase.
Each step respects a specific form of authority and consistency ,
ensuring that the runtime maintains predictable scheduling even when dozens of asynchronous operations are competing for attention.
Nobody is malfunctioning.
Nobody is betraying the system.
They are simply following the rules that keep the entire runtime coherent.
The Quiet Lesson
Most developers encounter the event loop in exactly the same way.
Something prints out of order.
The output feels wrong.
The runtime briefly feels like it is lying to you.
But the truth is far less dramatic.
The system was doing exactly what it promised to do the entire time.
You just had not met all the characters yet.
And once you understand the city they inhabit,
the strange behavior stops feeling cursed and starts feeling inevitable.
You recognize the rhythm.
You respect the priorities.
And suddenly the event loop stops being mysterious.
That’s not failure.
That’s evolution.
The “I liked this” Starter Pack:
Don’t let your fingers get lazy now.
- Like : It tells me this was worth writing.
- A Comment: Tell me your thoughts, your favorite snack, or a better title for this blog.
- Boost it: Especially with that one developer who definitely needs this.
Thanks for being here. It genuinely helps more than you know!
Find me elsewhere:
- Professional stuff: linkedin.com/in/Aaroophan
- Code stuff: github.com/Aaroophan
- UI stuff: aaroophan.dev/Aaroophan
- Life stuff: instagram.com/Aaroophan
Top comments (0)