- Published on
Event Loop in Nodejs
- Authors
- Name
- Ganesh Negi
Node Js Event Loop explained
The Nodejs Event Loop is a mechanism that handles asynchronous operations without blocking the other operations.
It Executes JavaScript synchronously first and then process asynchronous operations.
It Delegates the heavy tasks like I/O ioerations, timers and network request to libuv library.
Libuv library is a library which is written in C to handle asynchronous operations.
This library manages a thread pool that offloads heavy tasks like file system, timers and network requests.
Once the thread pool completes its tasks, it sends callbacks to the event queue. The event loop processes the callbacks but only in case of when the call stack is empty.
What is event loop in node js / nodejs event loop ?
The Event Loop is a mechanism that handles asynchronous operations without blocking the other operations.
It Executes JavaScript synchronously first and then process asynchronous operations.
How does an Event loop work

The CPU can handle multiple requests simultaneously without being blocked by others. In an event loop model, performance is significantly improved compared to a thread-based model, as threads require substantial memory for execution within the kernel.
Why does the event loop matter in Node.js?
The event loop is a key component of Node.js, enabling it to handle multiple tasks simultaneously without relying on multiple threads. It powers Node’s asynchronous nature, allowing operations to run efficiently without blocking execution.
By utilizing a non-blocking approach, the event loop optimizes performance and resource usage, ensuring that applications respond quickly even under heavy workloads. Unlike a thread-based model, which consumes more memory, the event loop efficiently manages requests, allowing the CPU to process many tasks at once with minimal overhead. This makes Node.js a high-performance choice for real-time applications and scalable systems.
The Actual Event Loop

The event loop in Node.js operates through several distinct phases, each handling specific types of tasks:
Timers – Executes setTimeout and setInterval callbacks once their delay has elapsed.
Pending Callbacks – Handles I/O callbacks deferred from the previous cycle.
Idle/Prepare – Used internally by Node.js for system-related operations.
Poll – Retrieves new I/O events and executes their callbacks. If there are no timers, it waits for new events.
Check – Executes callbacks from setImmediate().
Close Callbacks – Runs cleanup functions for closed connections, like socket.on('close', callback).
Incoming Connections & Data – Handles network requests and incoming data from sources like HTTP servers.
The timers phase is one of the most critical parts of the event loop. This phase executes callbacks registered with setTimeout() and setInterval(), allowing developers to schedule tasks and monitor event loop activity. Once a timer expires, the event loop processes its callback before moving to the next phase.
The poll phase is where Node.js checks for completed asynchronous operations and executes their callbacks. If there are no pending timers, the event loop may wait for new I/O events before continuing.
Next comes the check phase, which executes callbacks registered with setImmediate(), ensuring they run as soon as the poll phase is complete. However, process.nextTick() holds the highest priority, executing immediately after the current phase, before moving to the next cycle.
Finally, the close callbacks phase handles cleanup tasks, such as closing network connections or managing errors. After this, the event loop starts over, ensuring that the application remains fast, responsive, and non-blocking.
Understanding Request Processing Time in Node.js
When a request is received in Node.js, it generally follows a synchronous processing flow, except when it needs to interact with a database or perform other I/O operations, which are handled asynchronously.
This means that for every request, there are two synchronous processes (SP) and one asynchronous process (AP) involved in handling the request.
Formula for Response Time
The response time can be calculated as:
Response Time=2SP+1AP
Where:
SP (Synchronous Processing) is the time taken for synchronous operations like parsing the request, middleware execution, and preparing the response.
AP (Asynchronous Processing) is the time taken for database queries, file system operations, or API calls.
Example Calculation
Let’s assume:
Synchronous processing (SP) = 10ms
Asynchronous processing (AP) = 10ms
Total Response Time=2(10)+10=30ms
This means that handling a single request takes 30ms from start to finish.
Calculating the Maximum Requests per Second
To determine how many requests a single CPU core can handle per second:
Requests per Second= (2SP)/1000ms
Using our values:
1000/2(10) = 1000/20 = 50 requests per second

💡 Why don’t we consider I/O wait time?
Since Node.js uses an event loop, it continues handling other requests while waiting for I/O operations to complete. This makes it highly efficient in handling concurrent connections.
Calculating Response Time for Multiple Requests in Node.js
When a Node.js server receives multiple requests simultaneously, it processes them one at a time in a non-blocking manner. However, since synchronous operations (SP) run sequentially within the event loop, later requests must wait for previous ones to finish before being processed.
Understanding the Queued Processing Order The first request is processed immediately.
The second and third requests wait for the previous ones to finish before execution.
This pattern continues, increasing the total response time for later requests.
Example of Processing Time for Three Requests Using the formula:
Response Time=SP×x+AP+(SP×(x−1)×2)
where:
SP = Synchronous Processing Time
AP = Asynchronous Processing Time
x = Request number
Given: SP = 10ms, AP = 10ms
First request: Response Time = 10(1) + 10 + (10(0) * 2) = 30ms
Second request: Response Time = 10(2) + 10 + (10(1) * 2) = 50ms
Third request: Response Time = 10(3) + 10 + (10(2) * 2) = 70ms
Thus, the third request is processed after 70ms.
Calculating Response Time for 100 Requests
For x = 100 requests, the time for the last request:
Response Time=10(100)+10+(10(99)×2)
=1000+10+1980 =2990ms
So, the 100th request takes approximately 2990ms before it is processed.
How to Reduce Execution Time? 🔹 1. Scale Your Servers
Deploy multiple Node.js instances across different CPUs.
Use load balancers (e.g., Nginx, AWS ELB) to distribute requests.
🔹 2. Optimize Async Operations
Use worker threads for CPU-heavy tasks.
Implement database connection pooling to handle multiple queries efficiently.
🔹 3. Implement Cluster Mode
Node.js runs on a single thread, but the cluster module can spawn child processes. Example:
const cluster = require("cluster");
const http = require("http");
const os = require("os");
if (cluster.isMaster) {
for (let i = 0; i < os.cpus().length; i++) {
cluster.fork(); // Create a worker for each CPU core
}
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end("Hello, World!");
}).listen(3000);
}
This distributes requests across multiple CPU cores for better performance.
🔹 4. Use Microservices
Instead of handling everything in a monolithic Node.js server, break it into smaller services that can scale independently.
🔹 5. Enable Caching
Cache frequent database queries using Redis or in-memory storage to reduce the time spent on async tasks.
Understanding Event Loop Delay in Node.js
Best Practices for Optimizing the Event Loop in Node.js
Ensuring an efficient event loop is crucial for maintaining high performance, scalability, and responsiveness in Node.js applications.
✅ Best Practices
1️⃣ Avoid Blocking the Event Loop
Never execute CPU-heavy synchronous tasks on the main thread.
Offload expensive operations to worker threads or child processes.
Example:
const { Worker } = require("worker_threads");
const worker = new Worker("./worker-task.js"); // Run expensive tasks in a worker thread
worker.on("message", (msg) => console.log("Worker Output:", msg));
2️⃣ Use Worker Threads for Heavy Computation Worker threads allow multi-threading in Node.js, keeping the main thread free for handling requests.
Piscina is a powerful library for managing worker thread pools.
Installation:
npm install piscina
Example using Piscina:
const { Piscina } = require('piscina');
const pool = new Piscina({ filename: './worker-task.js' });
async function runTask() {
const result = await pool.run({ input: 10 });
console.log('Result:', result);
}
runTask();
Piscina manages a pool of worker threads and prevents excessive CPU usage.
You can also monitor how many jobs are queued, helping optimize performance.
3️⃣ Handle I/O Operations Efficiently
Use async I/O operations instead of synchronous ones.
Bad Example (Blocking File Read):
const data = fs.readFileSync("file.txt"); // Blocks event loop
console.log(data.toString());
Good Example (Non-Blocking File Read):
fs.readFile("file.txt", (err, data) => {
if (err) throw err;
console.log(data.toString());
});
4️⃣ Use setImmediate() Instead of process.nextTick() process.nextTick() prioritizes execution over I/O, which can block the event loop if overused.
Instead, use setImmediate() to allow the event loop to process I/O tasks first.
Bad Example:
process.nextTick(() => console.log("Executed immediately!"));
Good Example:
setImmediate(() => console.log("Executed in the next event loop cycle!"));
5️⃣ Reduce Memory Consumption & Garbage Collection (GC) Overhead Optimize objects to reduce memory pressure and avoid frequent GC cycles.
Use WeakMap for temporary references that don’t prevent GC.
🚨 Consequences of Blocking the Event Loop
🔴 1. Reduced Performance The server struggles to handle multiple concurrent requests.
Slower response times for users.
🔴 2. Increased Latency Users experience delays as requests queue up behind blocked operations.
🔴 3. Unresponsive Application If the event loop is fully blocked, the application freezes and becomes unresponsive.
Conclusion
✅ Offload heavy computations using worker threads & Piscina.
✅ Use async I/O instead of blocking synchronous calls.
✅ Avoid excessive process.nextTick(), use setImmediate() instead.
✅ Monitor event loop delay to detect performance issues.
Most commonly inteview questions asked on Event loop in Nodejs
What is the role of the Event Loop in JavaScript execution?
Can you describe the process by which the Event Loop functions in JavaScript?
What are the key components that make up the Event Loop in JavaScript?
How does JavaScript’s Event Loop manage asynchronous operations?
What distinguishes the Call Stack from the Task Queue in JavaScript?
What is the concept of the message queue in JavaScript’s Event Loop?
How does the Event Loop ensure non-blocking behavior in JavaScript?
What are the differences between the microtask queue and the task queue in JavaScript?
How does the Event Loop coordinate the execution of both synchronous and asynchronous code in JavaScript?
Why is it essential for developers to grasp the workings of the Event Loop in JavaScript?
What is the function of the Call Stack in JavaScript's execution flow?
In what ways does the Event Loop handle concurrency in JavaScript?
How does the Event Loop manage Promise resolution in JavaScript?
What is the relationship between async/await and the Event Loop in JavaScript?
How does the Event Loop deal with long-running tasks in JavaScript?
What strategies can be employed to optimize performance with respect to the Event Loop in JavaScript?
How can race conditions arise in JavaScript, and how does the Event Loop prevent them?
How are multiple Event Loops managed in scenarios involving iframes or web workers?
What are the differences in how the Event Loop operates in browsers versus Node.js environments?