DEV Community

Aaroophan Varatharajan
Aaroophan Varatharajan

Posted on Originally published at javascript.plainenglish.io on

The Node.js Event Loop: Everyone Gets a Turn… But Some Get VIP Access

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

A digital illustration for The Origami Software Engineer blog showing a developer in a hoodie viewed from behind, scratching his head in confusion. He is standing at a control console looking into a massive, glowing, futuristic maze that represents the inner workings of Node.js. The winding, neon pathways are labeled with event loop phases like “SYNCHRONOUS CALL STACK,” “TIMERS QUEUE (setTimeout),” “CHECK PHASE (setImmediate),” and “MICROTASK QUEUE (process.nextTick).” Bold text at the top reads
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');
Enter fullscreen mode Exit fullscreen mode

And the output that eventually arrives is short, and deceptively revealing.

Start
End
nextTick
Timeout
Immediate
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode

And so the console receives the next line.

nextTick
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode

The console prints the final message.

Immediate
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)