Web DevelopmentBackend

Node.js Event Loop Explained Clearly (With Real Execution Examples)

Vishwam DhavaleWednesday, March 4, 2026
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:

  1. process.nextTick() callbacks
  1. promise microtasks
  1. 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:

  1. Request arrives
  1. Event loop schedules the database query
  1. Database query runs asynchronously
  1. Event loop continues processing other requests
  1. Database result returns
  1. Callback executes and sends the response
This design allows Node.js servers to handle large numbers of concurrent requests without spawning thousands of threads.

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.

#Node.js#JavaScript#Event Loop#Backend
V

Written by Vishwam Dhavale

Full stack developer building scalable web & mobile systems. Founding Engineer with a passion for clean architecture and great DX.