Have you ever wondered what happens behind the scenes when you use a web or mobile application built with Node.js? How does your HTTP request get processed? How does Node.js handle so many operations — like reading files, querying databases, or sending responses — without crashing or slowing down? The secret lies in the Node.js Event Loop, a core mechanism that enables non-blocking, asynchronous execution in Node.js.
Node.js Event Loop – The Traffic Controller of Your Code
The Node.js Event Loop is the core component that manages the execution order of all tasks in your application — from synchronous operations to async callbacks and timers.
Think of the Event Loop as an air traffic controller in an airport — it doesn’t fly or fuel planes, but it controls the order and timing of every movement.
What the Event Loop Does:
✅ Manages the execution order of:
📚 Synchronous code
🔄 Microtasks (Promise.then()
, process.nextTick()
)
⏱ Macro-tasks (setTimeout()
, setImmediate()
, I/O callbacks)
“So, if a question comes to mind: Is the Event Loop responsible for all task execution in Node.js?”
Here’s the corrected answer:
The Node.js Event Loop is not responsible for executing all tasks, but it coordinates and schedules tasks between different components like the Call Stack, Microtask Queue, Macro-task Queue, and Thread Pool.
Below given the detail of component and which library executed it and what is role of event loop

Let’s dive deep into a real-life use case to understand the flow of a request and the role of the Event Loop in handling it.
Real-Life Use Case: A User Sending an HTTP Request
Imagine a user opening a web application and clicking a button to fetch some data. Here’s how the request is handled behind the scenes in a Node.js application:
HTTP Request (GET / POST / PUT / DELETE)
The user action (like submitting a form or clicking a button) triggers an HTTP request to the backend — typically a GET
, POST
, PUT
, or DELETE
request.
Routing in Node.js
The request hits the Node.js server, where the URL and HTTP method are matched against the defined routes using frameworks like Express.js.
const express = require('express');
const fs = require('fs');
const app = express();
app.get('/api/data', (req, res) => {
console.log('1 - Start');
// ✅ Synchronous Code (V8, Call Stack)
const result = 2 + 2; // simple operation
console.log(`2 - Calculated result: ${result}`);
// ✅ Microtask: Promise (V8 handles → Microtask Queue)
Promise.resolve().then(() => {
console.log('3 - Promise resolved');
});
// ✅ Macro-task: setTimeout (Node API + libuv → Timer Phase)
setTimeout(() => {
console.log('4 - Timeout executed');
}, 0);
// ✅ Async I/O: File read (Node API + libuv → Poll Phase → Macro-task)
fs.readFile('./data.txt', 'utf8', (err, data) => {
console.log('5 - File read complete');
res.send('Response sent'); // send response to client
});
console.log('6 - End of route handler');
});
app.listen(3000, () => console.log('Server running on port 3000'));
Execution of Code (Sync + Async)
It’s important to understand what type of code goes where, and which component of the Node.js runtime is responsible for storing and managing it.
See Blow 👇
Code Type | Where It’s Stored | Responsible Component | Why It’s Stored There |
---|---|---|---|
Synchronous code | Call Stack | ✅ V8 Engine | JavaScript is single-threaded, and sync code must run immediately and in order. |
Promise callbacks | Microtask Queue | ✅ V8 Engine | Promises are async, but are given high priority to run immediately after the current stack. |
process.nextTick() | Internal Microtask Queue | ✅ Node.js Internals | Designed to run before Promises, giving developers tighter control. |
setTimeout() , setImmediate() | Macro-task Queue | ✅ libuv (via Node APIs) | Timers are deferred and scheduled for future execution in separate phases. |
I/O callbacks (e.g. fs.readFile ) | Macro-task Queue (Poll Phase) | ✅ libuv + Thread Pool | File system and network operations run in background threads and notify when complete. |
So, with the above table description and the above JavaScript code, we can easily relate which type of code stores where and which component is responsible for managing their execution.
So, based on the table and the JavaScript example above, we can easily understand where each type of code is stored and which component is responsible for managing its execution
Let’s understand why these components store tasks where they do — and how the Node.js Event Loop manages the flow between them.
🔹 1. V8 Stores Synchronous Code in the Call Stack
Why?
- JavaScript is single-threaded by design.
- The call stack executes code top-down and synchronously.
- Every function call is stacked and popped one at a time.
V8’s Rule:
“If it’s plain JavaScript, it must execute immediately and in order → store it on the call stack.”
🔹 2. V8 Stores Promises in the Microtask Queue
Why?
- Promises are designed to be asynchronous, but still high-priority.
- They resolve after the current synchronous code, but before timers or I/O.
V8’s Rule:
“If it’s a microtask like
Promise.then()
, queue it to run right after the current synchronous code.”
✅ This keeps async logic predictable and smooth — especially in frameworks and render pipelines (like React).
🔹 3. Node.js Internals Put process.nextTick()
in a Special Queue
Why?
- It’s a Node.js-only feature, not in browsers.
- It’s used to defer execution until the stack clears, but before any other microtask (even Promises).
Node’s Rule:
“
process.nextTick()
is ultra-high priority → run it before all other microtasks.”
🔹 4. libuv Stores Timers and I/O in the Macro-task Queue
Why?
setTimeout
,fs.readFile
, and network events are all asynchronous system-level operations.- These are offloaded to the OS or thread pool, and libuv schedules their callback once completed.
libuv’s Rule:
“If the task involves system I/O or delay, queue it for later execution in the macro-task phase.”
Let’s further understand the details of the call stack
Eventually, all code(Sync, Async, Timers, Promises, nextTick()
) that gets executed is pushed to the call stack, but not all of it is pushed by the Event Loop, and not all of it starts in the same place.
Code Type | Pushed to Call Stack? | Pushed by | When It Happens |
---|---|---|---|
Synchronous Code | ✅ Yes | V8 (directly) | Immediately during parsing/execution |
Promise.then() | ✅ Yes | Event Loop (Microtask Phase) | After sync code finishes |
process.nextTick() | ✅ Yes | Node.js Internals | After sync code, before Promises |
setTimeout() | ✅ Yes | Event Loop (Timer Phase) | After the timer expires |
I/O Callbacks | ✅ Yes | Event Loop (Poll Phase) | When file/network I/O completes |
Key Point
Only synchronous code is pushed directly by V8.
All asynchronous callbacks (timers, Promises, I/O) are pushed to the call stack later, by the Event Loop, once they are ready to run.