The Node.js Event Loop Explained


In this article we will learn about the execution order of event loop in Node.js .You would be able to predict the output of any async and sync combined codes after going through the article.
Introduction
Asynchronous Codes
Certains tasks such as reading files , fetching data from API’s (promises) etc are slow and take more time than other synchronous codes. They are known as asynchronous codes .The operations don’t necessarily appear in the order they appear because if they did , then the entire program would come to a hault until the particular operation completes.
Image source: Google
JavaScript’s Single Thread and How Async Works in Browsers:
We all know that JavaScript is a single-threaded language. Since Node.js is built upon JavaScript, it also runs on a single thread.
Usually, all operations are meant to run on this one thread.
In web browsers, asynchronous operations like timers, network requests, and DOM events are handled by Web APIs provided by the browser.
These APIs offload the work to background threads and use a callback mechanism to return results once complete. This ensures that the operations on the main thread keep executing without being blocked.
How Async works in Node.Js
Similarly to handle the async tasks , Nodejs uses libuv under the hood to manage asynchronous operations.Libuv provides a thread pool and an event loop, which allow Node.js to perform non-blocking I/O operations like file system access, network communication, and timers.
Just replace the Web API environment with libuv in your understanding of the above diagram
Event Loop : The Manager
We just saw that an async operation starts (like reading a file), Node /Browser offloads it to the callback queues. Meanwhile, the main thread does not wait — it continues running all synchronous code in the call stack.
The Event Loop is the manager that monitors the call stack, and once it's empty, it pulls callbacks from the queue for execution.
Explanation:- Once the async operation completes in the background ,The event loop then takes callbacks from this queue and pushes them onto the main thread's call stack to run one by one when the call stack becomes empty.
Although we say "queued," it's not a strict first-come-first-served line. Think of it like a school cafeteria line where VIPs like teachers or staff can skip ahead. Similarly, in the Event Loop, some tasks (like microtasks or high-priority callbacks) are given priority, so they’re processed before others, even if they arrived later.
Types of Queues(In Priority)
process.nextTick()
QueueMicrotask Queue (e.g., Promises)
Macrotask/Event Loop Phase Queues
Now under MacroTask queues , there are many stages called as phases
Macrotasks include:
🕒 Timers Phase
Executes callbacks fromsetTimeout()
andsetInterval()
if their time has expired.📦 I/O Callbacks Phase
Handles callbacks from completed I/O operations (e.g., file system access, network).⏳ Idle, Prepare Phase
Mostly used internally by Node.js to prepare for the next phase.🌀 Poll Phase
Waits for incoming I/O events and processes them. If no tasks are pending, it may wait or move on.✅ Check Phase
ExecutessetImmediate()
callbacks — these are slightly prioritized over timers if both are scheduled simultaneously.🔒 Close Callbacks Phase
Executes cleanup callbacks likesocket.on('close', ...)
.
Golden rules followed:
These are the most important rules that is observed.
If you’re in phase X, even if new callbacks are scheduled for phase Y or Z, phase X runs to completion first.After that, event loop checks other phases in order of priority and runs those callbacks next.
The event loop executes callbacks recursively within a phase until all callbacks and microtasks (and
process.nextTick
callbacks) queued during that phase are completely drained.Only when the current phase’s queue and all microtasks/nextTick callbacks are fully drained, the event loop proceeds to the next phase.
Execution happens in the order things are scheduled, not the order they are written inside nested functions.
Imagine you're in a school cafeteria line waiting to order food.
You're just about to order when a teacher walks in.
Now, you don’t give up your spot you say:
“Ma’am, you can stand right behind me and go before anyone else ,after I’m done.”
Then you turn to your friend behind you and say:
“Hey, wait until the teacher finishes before you move.”
Don’t worry check the example below to understand.
const fs = require('fs');
console.log('A');
setTimeout(() => {
console.log('B');
process.nextTick(() => console.log('C'));
Promise.resolve().then(() => console.log('D'));
}, 0);
fs.readFile(__filename, () => {
console.log('E');
process.nextTick(() => console.log('F'));
Promise.resolve().then(() => console.log('G'));
});
Promise.resolve().then(() => console.log('H'));
process.nextTick(() => console.log('I'));
console.log('J');
All synchronous code is executed first, top to bottom:
console.log('A')
console.log('J')
process.nextTick()
callbacks run immediately after sync code:console.log('I')
Microtasks (Promises) are then executed:
console.log('H')
Now the Event Loop moves to the macrotask queue, starting with the Timer phase:
setTimeout
fires and prints:console.log('B')
Inside this callback:
A
process.nextTick()
is queued (console.log('C')
)A Promise is also queued (
console.log('D')
)
Since nextTick
and Promises are always processed immediately after the current callback, they run next:
console.log('C')
(fromprocess.nextTick
)console.log('D')
(from Promise)
Next, the Event Loop goes to the I/O phase, where the
fs.readFile()
callback is ready:console.log('E')
Inside that callback:
A
process.nextTick()
is queued (console.log('F')
)A Promise is queued (
console.log('G')
)
Again, after the current macrotask:
console.log('F')
(fromprocess.nextTick
)console.log('G')
(from Promise)
Thankyou!!
Subscribe to my newsletter
Read articles from K Raghav directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

K Raghav
K Raghav
I am a B.Tech Computer Science student in my second year, aspiring to become a software engineer with a strong interest in web development. Currently, I am exploring development while actively practicing Data Structures and Algorithms (DSA) to enhance my problem-solving skills and speed. I am passionate about contributing to open-source projects and aim to work for a product-based startup or an MNC in the future.