Demystifying the Node.js Event Loop: A Deep Dive Into the Engine’s Core
If you’ve been writing Node.js for a while, you’ve undoubtedly heard the phrase "single-threaded, asynchronous, and non-blocking." If you…
Demystifying the Node.js Event Loop: A Deep Dive Into the Engine’s Core
Source : https://www.rapid7.com
If you’ve been writing Node.js for a while, you’ve undoubtedly heard the phrase "single-threaded, asynchronous, and non-blocking." If you want read Node.js architecture overview before proceeding read my article here : Node.js architecture explained so simply your grandma could get it.
And if you are aware of architecture you might even know that the Event Loop is the secret sauce that makes it all work.
But if someone asked you to explain the exact order in which Node.js executes a setTimeout, a database callback, and a resolved Promise, could you do it?
Most developers treat the Event Loop like a black box—magic happens inside, and callbacks come out. But pulling back the curtain on this engine isn't just an academic exercise; it’s the key to writing highly optimized code, debugging race conditions, and completely crushing senior-level technical interviews.
Let’s dismantle the black box and look at how the Event Loop actually processes code under the hood.
The Biggest Myth: "The Single Queue"
When people first learn about the Event Loop, they usually picture something like a grocery store checkout line: tasks line up in a single queue, and Node’s single thread processes them one by one.
This is entirely wrong.
In reality, the Event Loop consists of multiple distinct phases, each managing its own unique queue of callbacks. When Node.js executes, it rides a continuous loop through these phases. We call one full rotation through these stages a tick.
Let’s look at the exact itinerary of this "merry-go-round" and see what happens at every stop.
The 6 Phases of the Event Loop
Every time the Event Loop takes a lap, it visits these phases in a strict, unyielding order. If a queue is empty, the loop simply skips it and moves to the next phase.
Source : https://getsdeready.com
1. Timers Phase
This is the starting line of a new tick. The Event Loop checks to see if any expired setTimeout or setInterval callbacks are waiting. If their countdowns have hit zero, the loop executes their callbacks here.
Note: The execution time isn't exact; it’s a guarantee of the minimum delay before execution.
2. Pending Callbacks Phase
This phase executes system-level callbacks that were deferred from the previous loop iteration. For instance, if a TCP socket attempts to connect and encounters an error (ECONNREFUSED), the operating system reports it, and Node reports that error callback right here.
3. Idle, Prepare Phase
This is a purely internal phase used by Node.js to sync its internal state and prepare for the next step. As an application developer, you can completely ignore this—no user code ever runs here.
4. Poll Phase (The Heavy Lifter)
This is where the real work happens. The Poll phase does two things:
It executes callbacks for finished I/O operations (reading a file from the disk, receiving data from an incoming HTTP request, or getting a response from a database query).
It calculates how long it should block and wait for new I/O events to finish. If the queue is empty and there are no immediate scripts or timers waiting, the loop will actually pause here to wait for an incoming request rather than spinning endlessly and wasting CPU cycles.
5. Check Phase
This phase belongs entirely to setImmediate(). If your code has passed through the Poll phase and there are scripts scheduled via setImmediate(), the loop moves here to execute them instantly rather than waiting for the next tick.
6. Close Callbacks Phase
The final cleanup crew. If a socket or a handle is closed abruptly (like socket.destroy() or stream.on('close', ...)), their cleanup callbacks are executed here right before the tick ends.
The VIP Line: Microtask Queues
If the 6 phases were the entire story, the Event Loop would be relatively straightforward. But there is a massive twist: The Microtask Queues.
Microtask queues do not belong to the Event Loop directly, but they have the power to completely interrupt it. There are two primary microtask queues:
- The process.nextTick() Queue (Highest priority)
- The Promise Queue (Handles resolved native promises, .then(), and async/await)
The Interruption Rule
The moment a phase finishes—or after every single individual callback inside a phase executes—the Event Loop pauses, checks the Microtask Queues, empties them completely, and only then resumes its normal schedule.
Think of microtasks like VIP pass holders at an amusement park. They don't wait for the ride to finish a full cycle; they cut to the front of the line the absolute second the current seat empties.
Let’s Play a Game: Can You Predict the Output?
The best way to solidify your understanding of the Event Loop is to look at a classic interview puzzle. Read through this snippet and try to guess what the console will print, and in what exact order:
const fs = require('fs');
console.log('1. Script Start');
setTimeout(() => {
console.log('2. setTimeout (Timer Phase)');
}, 0);
setImmediate(() => {
console.log('3. setImmediate (Check Phase)');
});
Promise.resolve().then(() => {
console.log('4. Promise (Microtask)');
});
process.nextTick(() => {
console.log('5. nextTick (Microtask VIP)');
});
console.log('6. Script End');
The Breakdown:
- Synchronous code always runs first. The main script executes sequentially, printing
1. Script Startand6. Script End. - While executing the main script, Node encounters the asynchronous calls and registers them to their respective phases/queues.
- Before the Event Loop even begins its very first official phase, it checks the microtasks.
**process.nextTickalways wins**, printing5. nextTick (Microtask VIP). - Next, it drains the rest of the microtask queue, printing
4. Promise (Microtask). - Now the Event Loop starts phase 1 (Timers) and finds our expired
setTimeout, printing2. setTimeout (Timer Phase). - Eventually, it reaches the Check phase, executing our
setImmediateand printing3. setImmediate (Check Phase).
Final Output:
1. Script Start
6. Script End
5. nextTick (Microtask VIP)
4. Promise (Microtask)
2. setTimeout (Timer Phase)
3. setImmediate (Check Phase)
The Ultimate Rule of Thumb
If you remember nothing else from this deep dive, remember this: Never block the Single Thread.
Because Node.js cycles through these queues sequentially, a massive, CPU-heavy operation (like calculating prime numbers or compressing a massive video file) running synchronously in your main code will trap the Event Loop. While your single thread is stuck processing that heavy math, it can’t move to the Poll phase to accept new requests or the Timers phase to clear timeouts. Your entire server effectively freezes.
Understanding the phases of the Event Loop changes you from a developer who just writes JavaScript to an engineer who fundamentally understands runtime mechanics. Use this blueprint to architect cleaner code, keep your microtasks lean, and build ultra-fast systems.
If you haven’t read my previous article on Node.js architecture overview. You can read my article here : Node.js architecture explained so simply your grandma could get it.
If you found this helpful, give it a few claps and follow along so you don’t miss the deep dive into Libuv Node.js engine coming up next!
메타데이터
- post_id
- 23ffdfa02216
- slug
- demystifying-the-node-js-event-loop-a-deep-dive-into-the-engines-core-23ffdfa02216
- url
- https://medium.com/@divyapawar43/demystifying-the-node-js-event-loop-a-deep-dive-into-the-engines-core-23ffdfa02216
- canonical_url
- https://medium.com/@divyapawar43/demystifying-the-node-js-event-loop-a-deep-dive-into-the-engines-core-23ffdfa02216
- author_url
- https://medium.com/@divyapawar43
- status
- ok
- fetched_at
- 2026-08-27 07:37:15