Paramclasses Academy - Blog

Tutorials Details

←
Post 13 of 40
→

How the Node.js Event Loop Works with Practical Examples

How the Node.js Event Loop Works with Practical Examples

October 29, 2026

✦ How the Node.js Event Loop Works with Practical Examples

❖ Introduction

Node.js is widely used for building web servers, REST APIs, real-time applications, and backend systems. One of the most important concepts behind Node.js is the Event Loop.

The Event Loop helps Node.js manage asynchronous operations such as timers, file handling, and network requests without blocking the main JavaScript execution thread while waiting for I/O operations.

For beginners, understanding the Event Loop makes it easier to understand why some messages appear before others, how asynchronous code executes, and how Node.js handles multiple requests efficiently.

In this tutorial, we will learn what the Node.js Event Loop is, how it works, the major event loop phases, and how to test its behavior with practical JavaScript examples.

✦ What Is the Node.js Event Loop?

The Event Loop is a mechanism that coordinates the execution of JavaScript callbacks and asynchronous operations in Node.js.

Node.js uses the V8 JavaScript engine to execute JavaScript. For many I/O operations, Node.js also relies on operating-system facilities and its underlying runtime, including libuv, to manage asynchronous work.

When an asynchronous operation is started, Node.js can continue executing other JavaScript rather than waiting for that operation to finish. Once the operation completes and its callback is ready to run, the runtime schedules the callback for execution.

The Event Loop helps determine when eligible callbacks are processed.

For example, a Node.js application might need to:

  • Read a file from the system.

  • Retrieve information from a database.

  • Receive an HTTP request.

  • Run a timer callback.

  • Send a response to a client.

The Event Loop helps coordinate these activities while JavaScript executes on its main thread.

❖ Why Is the Event Loop Important?

Understanding the Event Loop is useful for several reasons:

1. Efficient I/O handling: Node.js can manage many I/O operations without waiting synchronously for each one.

2. Application responsiveness: The application can continue processing other work while an I/O operation is in progress.

3. Better debugging: Understanding execution order helps explain why callbacks sometimes run later than expected.

4. Performance optimization: Developers can identify code that blocks the Event Loop and slows down other requests.

5. Reliable backend development: Knowledge of the Event Loop helps developers build better APIs and real-time applications.

✦ A Simple Event Loop Example

Let's begin with a basic program.

Create a file named event-loop.js:

console.log("Start");

setTimeout(() => {
    console.log("Timer completed");
}, 1000);

console.log("End");

Run it using the terminal:

node event-loop.js

Output:

Start
End
Timer completed

❖ How Does This Code Work?

  • First, Node.js executes console.log("Start").

  • Next, it registers the timer callback.

  • JavaScript continues and prints End.

  • After the timer becomes eligible to run, its callback executes when the runtime reaches the appropriate point.

The timer specifies a minimum delay, not an exact execution time. If the main thread is busy, the callback may execute later.

✦ Synchronous Code vs Asynchronous Code

Synchronous code executes in sequence. Each statement runs before the next statement begins.

Asynchronous operations allow some work to complete later, so JavaScript can continue executing other statements in the meantime.

Consider this example:

console.log("Task 1");

setTimeout(() => {
    console.log("Task 2");
}, 0);

console.log("Task 3");

Output:

Task 1
Task 3
Task 2

Even with a delay of 0, the timer callback does not execute immediately. The current synchronous code completes first.

This is a common beginner question: Why does setTimeout(..., 0) not run instantly?

The reason is that a timer callback must wait until it is eligible to run and the runtime gets an opportunity to process it.

✦ The Major Phases of the Node.js Event Loop

The Node.js Event Loop has several phases. Each phase is associated with particular types of callbacks.

❖ 1. Timers Phase

The timers phase processes eligible callbacks scheduled by functions such as:

  • setTimeout()

  • setInterval()

Example:

setTimeout(() => {
    console.log("Timer callback");
}, 500);

The callback becomes eligible after approximately the specified delay, but its execution can be later depending on the workload and event loop scheduling.

❖ 2. Pending Callbacks Phase

This phase handles certain I/O callbacks deferred from a previous event loop iteration.

Most beginners do not need to manipulate this phase directly, but knowing that it exists helps explain Node.js internals.

❖ 3. Idle and Prepare Phase

These phases are primarily used internally by libuv. Application developers generally do not interact with them directly.

❖ 4. Poll Phase

The poll phase is important for handling I/O-related callbacks. Depending on the state of the event loop, Node.js can process available I/O events or wait for new events.

Examples of operations that may involve I/O include file system and network activity.

❖ 5. Check Phase

The check phase processes callbacks scheduled using setImmediate().

Example:

setImmediate(() => {
    console.log("Executed in the check phase");
});

setImmediate() is useful when you want to schedule a callback for the check phase of the event loop.

❖ 6. Close Callbacks Phase

This phase processes certain close-event callbacks, such as a socket's close event.

Example:

const net = require("net");

const server = net.createServer();

server.on("close", () => {
    console.log("Server closed");
});

server.listen(3000, () => {
    server.close();
});

This program creates a server, starts listening, and closes it. The close callback prints a message when the server's close event is emitted.

✦ Understanding the Event Loop with Promises

Node.js also processes Promise handlers, which are commonly called microtasks.

Let's examine a simple example:

console.log("Start");

setTimeout(() => {
    console.log("Timeout callback");
}, 0);

Promise.resolve().then(() => {
    console.log("Promise callback");
});

console.log("End");

Typical output:

Start
End
Promise callback
Timeout callback

❖ Why Does the Promise Callback Run First?

First, Node.js executes the synchronous statements and prints Start and End.

The Promise handler is scheduled as a microtask. After the current JavaScript stack finishes, the runtime processes the Promise microtask before moving on to the timer callback in this example.

This illustrates why Promise handlers often run before timer callbacks registered in the same synchronous section.

✦ Understanding setTimeout() vs setImmediate()

Both setTimeout() and setImmediate() schedule callbacks, but they serve different purposes.

Feature setTimeout() setImmediate()
Main purpose Schedule a callback after a delay Schedule a callback for the check phase
Timing Becomes eligible after the delay Runs when the event loop reaches the check phase
Typical use Delayed tasks Work scheduled for a later event loop phase
Exact timing guaranteed? No No

 

   

Consider this code:

setTimeout(() => {
    console.log("Timeout");
}, 0);

setImmediate(() => {
    console.log("Immediate");
});

When run from the top level of a script, the order of these two callbacks is not guaranteed to be the same on every run. It can depend on timing and runtime conditions.

However, when scheduled within an I/O callback, setImmediate() will generally run before a zero-delay timer scheduled there.

For example:

const fs = require("fs");

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log("Timeout");
    }, 0);

    setImmediate(() => {
        console.log("Immediate");
    });
});

In this I/O callback example, Immediate is expected before Timeout, because setImmediate() callbacks are processed in the check phase following the poll phase.

✦ What Is Blocking the Event Loop?

Blocking happens when JavaScript performs work that prevents the main thread from moving on to other callbacks.

For example:

console.log("Start");

const start = Date.now();

while (Date.now() - start < 3000) {
    // Deliberately block the main JavaScript thread
}

console.log("Finished blocking");

This program keeps the main JavaScript thread busy for about three seconds. During that time, other JavaScript callbacks cannot run on that thread.

Blocking the Event Loop can delay:

  • Incoming request handling.

  • Timer callbacks.

  • Promise handlers.

  • Other JavaScript callbacks.

Avoid unnecessary CPU-intensive loops in request handlers. For demanding computation, consider worker threads or an external processing service.

✦ Practical Project: Build a Simple Timer Demonstration

Let's create a small program that demonstrates synchronous execution, Promise callbacks, and timers together.

Create event-demo.js:

console.log("1. Application started");

setTimeout(() => {
    console.log("4. Timer callback executed");
}, 1000);

Promise.resolve().then(() => {
    console.log("3. Promise callback executed");
});

console.log("2. Synchronous code completed");

Run the program:

node event-demo.js

Output:

1. Application started
2. Synchronous code completed
3. Promise callback executed
4. Timer callback executed

This small project demonstrates the difference between synchronous execution, Promise microtasks, and timer callbacks.

Try changing the timer delay, adding another Promise handler, or creating two timers with different delays. Observe how the output changes.

✦ Common Event Loop Mistakes Beginners Should Avoid

❖ Assuming a zero-delay timer runs immediately. It schedules a callback; it does not interrupt the current synchronous code.

❖ Believing Node.js never blocks. CPU-heavy JavaScript can block the main thread.

❖ Expecting timers to run at an exact time. Timer delays are minimum thresholds, not precise guarantees.

❖ Confusing Promises with timer callbacks. Promise handlers and timers follow different scheduling rules.

❖ Ignoring asynchronous errors. Handle rejected Promises and errors from callbacks appropriately.

✦ Learn Node.js Practically with Param Classes

The Event Loop is a fundamental topic for anyone learning Node.js backend development. Once you understand it, concepts such as Express.js, REST APIs, database operations, and real-time applications become easier to follow.

At Param Classes, learners can strengthen their programming knowledge by practising JavaScript, Node.js, asynchronous programming, and project-based development.

Explore programming tutorials at Param Classes. For website development and digital marketing services, visit ParamWebInfo.

❖ Frequently Asked Questions (FAQs)

1. What is the Event Loop in Node.js?

The Event Loop coordinates the execution of eligible callbacks and asynchronous operations, helping Node.js handle I/O efficiently.

2. Is Node.js single-threaded?

JavaScript generally runs on the main thread, but Node.js can use operating-system facilities, a worker pool, and worker threads for certain operations and workloads.

3. What is the poll phase?

The poll phase handles many I/O-related callbacks and determines whether the event loop should process available events or wait for new ones.

4. What is the difference between setTimeout() and setImmediate()?

setTimeout() schedules a callback after a minimum delay, while setImmediate() schedules a callback for the check phase.

5. Why does a Promise callback often run before a timer callback?

Promise handlers are processed as microtasks after the current JavaScript stack completes, before the timer callback in common examples.

6. How can I prevent Event Loop blocking?

Avoid long-running synchronous work in request handlers. Use asynchronous I/O and consider worker threads or separate services for CPU-intensive tasks.

✦ Conclusion

The Node.js Event Loop is a key part of how Node.js coordinates asynchronous work. By understanding synchronous execution, timers, I/O callbacks, Promise microtasks, and the check phase, developers can better predict program behavior and troubleshoot unexpected execution order.

Practise the examples in this tutorial, experiment with timers and Promises, and then apply these concepts to a real Node.js API.

Keep learning and keep building with Param Classes!

←
Post 13 of 40
→