The Node.js Event Loop Explained
How a Single Thread Manages Thousands of Tasks Without Breaking
The Problem: Single Thread, Many Requests
Node.js runs on a single thread. That means it can execute only one piece of code at a time.
At first glance this sounds like a limitation.
Imagine a server receiving,
A file read request
A database query
An API call
Another user request
If Node.js handled these one by one, everything would slow down quickly.
So the question is:
How does Node.js handle thousands of requests without getting stuck?
The answer is the Event Loop
What is the Event Loop?
The event loop is the task manager of Node.js.
It constantly watches,
What needs to be executed now
What is waiting to be executed
What has finished in the background
And then it decides what runs next.
You can think of it like a manager in a restaurant,
Orders come in (tasks)
Some are quick (serve water)
Some take time (cook food)
The manager keeps things moving without blocking everything
Why Node.js Needs an Event Loop
Because Node.js is single-threaded, it cannot
Wait for slow operations
Block execution
If it did, the entire server would freeze.
So instead:
It delegates slow work (like file reading, DB calls) to the system
Continues handling other requests
Comes back when the work is done
The event loop makes this coordination possible.
Call Stack vs Task Queue
To understand the event loop, you need two simple ideas,
1. Call Stack
Where code executes
Works li LIFO (Last in, First Out) order
Only one function runs at a time
function greet() {
console.log("Hello");
}
greet();
Flow,
greet()goes into the call stackExecutes
Leaves the stack
2. Task Queue
Holds tasks that are ready to run later
Works in FIFO (First In, First Out) order
How They Work Together
The event loop does this continuously,
Check if the call stack is empty
If empty --> pick a task from the queue
Push it to the call stack
Execute it
This cycle runs forever
How Async Operations Are Handled
Let’s look at a simple example,
import fs from "fs";
console.log("Start");
fs.readFile("./file.txt", "utf-8", (err, data) => {
console.log("File read complete");
});
console.log("End");
What actually happens:
"Start"logsfs.readFileis sent to the system (background)"End"logs immediatelyOnce file reading is done --> callback goes to task queue
Event loop pushes it to call stack
"File read complete"logs
Output
Start
End
File read complete
Node.js does not wait for file reading
Timers vs I/O Callbacks
Not all async tasks are treated exactly the same.
1. Timers (setTimeout, setInterval)
setTimeout(() => {
console.log("Timer done");
}, 0);
Scheduled after a delay
Goes to a timer queue
Runs only when call stack is empty
2. I/O Callbacks (file, DB, network)
import fs from "fs";
fs.readFile("./file.txt", () => {
console.log("I/O done");
});
Triggered when background work finishes
Added to a queue
Executed when event loop picks it
Important Insight
Even with 0ms delay,
setTimeout(() => console.log("Timer"), 0);
console.log("Sync");
Output
Sync
Timer
Because
Synchronous code always runs first
Event loop only runs queued tasks after stack is empty
Event Loop as a Queue Manager
Think of:
Call Stack --> Chef cooking one dish at a time
Task Queue --> Orders waiting
Event Loop --> Waiter managing flow
Flow:
Chef finishes current dish
Waiter brings next order from queue
Repeat
Meanwhile:
Long tasks (like baking) happen separately
Once done → added back to queue
Role of Event Loop in Scalability
This is where Node.js becomes powerful.
Because of the event loop,
Node.js does not block
Can handle many concurrent requests
Uses less memory
Works great for,
APIs
Real-time apps
Streaming services
Instead of:
Creating new threads for every request
Node.js does:
Reuses a single thread + event loop
Small Edge Case to Understand
If you write blocking code,
while (true) {}
The event loop gets blocked
No other task runs
Server becomes unresponsive
This shows:
The event loop is powerful, but only if you don’t block it