Node.js Event Loop Explained Clearly (With Real Execution Examples)
Node.js powers a huge portion of modern backend systems, yet one concept still confuses many developers:
the event loop.
You often hear statements like:
* "Node.js is single-threaded"
* "Node.js uses non-blocking I/O"
* "The event loop handles concurrency"
But what actually happens under the hood when your server processes requests?
Understanding the event loop is critical if you want to build scalable backend systems, debug async bugs, or optimize performance.
In this article we'll break down:
* how the Node.js event loop works
* the phases of the event loop
* the difference between microtasks and macrotasks
* real execution examples developers encounter in production
Why the Event Loop Exists
JavaScript was originally designed for browsers, where applications needed to handle:
* user interactions
* network requests
* timers
* rendering updates
without freezing the UI.
Instead of running multiple threads, JavaScript uses an event-driven architecture.
Node.js adopted this same idea for servers.
Instead of creating a new thread for every incoming request, Node.js relies on:
* the event loop
* non-blocking I/O
* background workers handled by libuv
This allows a single process to handle thousands of concurrent connections efficiently.
The Core Idea: One Thread, Many Tasks
Node.js runs JavaScript on a single main thread.
That sounds limiting, but heavy operations such as file access or networking are handled outside the main thread.
The event loop simply coordinates when callbacks should execute.
Example:
setTimeout(() => {
console.log("Timer finished")
}, 1000)
console.log("Start")
Output:
Start
Timer finished
The timer does not block execution. Node registers the timer and continues running other code.
Event Loop Phases
The event loop runs continuously through several phases. Each phase processes a specific type of callback queue.
1. Timers Phase
Handles callbacks scheduled by:
setTimeout()
setInterval()
These callbacks execute after the specified delay has passed.
2. Pending Callbacks
Handles system-level callbacks related to certain I/O operations.
Examples include:
* TCP errors
* system-level networking callbacks
Most developers rarely interact with this phase directly.
3. Idle / Prepare
This phase is used internally by Node.js to prepare for the next stage of the event loop.
Developers typically do not interact with this phase.
4. Poll Phase
This is the most important phase for most backend applications.
It handles:
* incoming network requests
* file system operations
* database responses
Example:
const fs = require("fs")
fs.readFile("file.txt", () => {
console.log("File read complete")
})
The file read operation runs outside the main thread. When it completes, the callback is added to the poll queue.
5. Check Phase
This phase handles callbacks scheduled using:
setImmediate()
Example:
setImmediate(() => {
console.log("Immediate callback")
})
These callbacks execute after the poll phase finishes.
6. Close Callbacks
The final phase handles cleanup events such as closed sockets.
Example:
socket.on("close", () => {
console.log("Connection closed")
})
Microtasks vs Macrotasks
Another important concept is the microtask queue.
Microtasks run before the event loop continues to the next phase.
Examples include:
* process.nextTick()
* Promise.then()
Example:
setTimeout(() => console.log("timeout"))
Promise.resolve().then(() => console.log("promise"))
process.nextTick(() => console.log("nextTick"))
Output:
nextTick
promise
timeout
Execution order:
process.nextTick()callbacks
- promise microtasks
- event loop phases
Real Backend Example
Consider a simple API endpoint.
app.get("/users", async (req, res) => {
const users = await db.query("SELECT * FROM users")
res.json(users)
})
Execution flow:
- Request arrives
- Event loop schedules the database query
- Database query runs asynchronously
- Event loop continues processing other requests
- Database result returns
- Callback executes and sends the response
Common Developer Misconceptions
Myth 1: Node.js Is Slow Because It Is Single-Threaded
Incorrect.
Node delegates heavy operations to background workers or the operating system.
Myth 2: Async Code Runs in Parallel
Async operations run concurrently, not necessarily in parallel.
Parallelism depends on the underlying system or worker threads.
Myth 3: Promises Run Inside the Event Loop
Promises run in the microtask queue, which executes before the next event loop iteration begins.
Why Understanding the Event Loop Matters
Understanding the event loop helps you:
* debug async bugs
* improve API performance
* avoid blocking operations
* design scalable backend services
Example of blocking code:
while(true) {}
This blocks the entire event loop and freezes your server.
Final Thoughts
The Node.js event loop is one of the most elegant concurrency models used in modern backend systems.
Once you understand:
* event loop phases
* microtask execution
* asynchronous delegation
it becomes much easier to reason about performance and scalability.
If you build production backend systems, understanding the event loop is not optional — it is foundational knowledge.
Written by Vishwam Dhavale — full-stack developer building modern web applications and scalable backend systems.
Written by Vishwam Dhavale
Full stack developer building scalable web & mobile systems. Founding Engineer with a passion for clean architecture and great DX.
Related Articles
How I Built My Developer Portfolio — From Idea to Production
A deep dive into building a modern developer portfolio with Next.js 16, Framer Motion, Three.js, and an interactive terminal. The story behind the design decisions, tech stack choices, and the journey from concept to production.
React 19: What Changed, What Broke, and What Developers Need to Know
React 19 introduced major changes like Server Components, Actions, and the new use() API. Here’s what changed, what broke, and how to upgrade safely.