Skip to main content

Command Palette

Search for a command to run...

The Node.js Event Loop

Updated
•5 min read•View as Markdown
The Node.js Event Loop

What is Event Loop in Node.js?

As Node.js uses only a single thread (main thread) to execute JavaScript code, it cannot directly handle asynchronous operations such as setTimeout(), promises, file system operations (fs), or network requests on its own.

Many of these operations take time to complete. For example, in setTimeout() the callback runs only after the given minimum delay. If Node.js waited for such operations to complete before continuing execution, the entire application would stop during that time, which would negatively impact performance.

To avoid blocking the main thread, Node.js uses an asynchronous architecture where these operations are handled outside the main thread by the runtime environment.

Once those operations complete (for example, when the minimum delay of setTimeout() is finished), their callbacks need to be executed in JavaScript on the main thread. This is where the Event Loop comes in.

The Event Loop continuously monitors different queues and phases (which we will discuss later). Whenever the Call Stack becomes empty (meaning the main thread has finished executing the current code) the Event Loop moves the pending callbacks from the queues to the Call Stack so they can be executed.

What are different phases in Event loop?

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

~ node.js docs

As you can see we have 6 phases but for this blog we will be discussing only 3 phases which are very important for us.

  1. timers

  2. poll

  3. check

Timers

Handles callbacks scheduled by timer functions.

• setTimeout()
• setInterval()

Poll

Handles I/O related callbacks and processes completed I/O operations.

Examples include callbacks from:

• File system operations (fs.readFile, fs.writeFile, etc.)
• Network requests
• Database operations
• Some system-level I/O tasks

Check

Handles callbacks scheduled using:

• setImmediate()

Working of Event loop.

It works very similar to our while(true) loop

For example -

while(true){
    console.log(1)
    console.log(2)
    console.log(3)

}

What will be the output?

1
2
3
1
2
3
1
... (infinite times)

That's how our event loop will work on these phases

phase1 -> phase2 -> phase3 -> phase1 -> phase2 -> phase3 -> phase1 ...

Which means the loop will look like this

Whenever the Event Loop enters a phase, it checks if there are callbacks waiting in that phase's queue. If callbacks are present and the call stack is empty, those callbacks are pushed to the call stack and executed.

This cycle keeps repeating as long as the Node.js application is running.

Guess the output

Now let us do a practical implementation.

import fs from 'fs'

setTimeout(()=>{
    console.log("Timer ends")
},0)

fs.readFile("README.md", () => {
    console.log("File reading is completed")
});

console.log("sync operation")

Let us do a dry run on what we just studied and guess the output.

Step 1 - Execute all synchronous code

Here we have console log("sync operations") as sync operation. Thus one console the first line will be

sync operations 

Step 2 - Register asynchronous operations

During execution, Node.js registers the async tasks:

• setTimeout() : Timer phase
• fs.readFile() : I/O operation handled by the system

These tasks are executed outside the main thread, and their callbacks will run later through the Event Loop.

Step 3 - Now we have completed all sync operations so lets move to complete async operations. Here as per our Event loop we have first phase as Timer, which means callback inside setTimeout() will be executed next.

Next line of output will be

sync operations
Timer ends

Step 4 - Next phase in event loop is poll which will executed I/O. Here we have fs.readfile() so now its callback will be executed in main thread.

Now our final output will be

sync operations
Timer ends
File reading is completed

Guess the output (Advanced)

So let's summarize what is sequence of Event loop

  1. Timer => example - setTimeout()

  2. Poll => I/O operations such as fs.readFile()

  3. Check => example - setImmediate()

So let us see this example

import fs from 'fs'

fs.readFile("README.md", () => {
    console.log("file reading ended")

    setTimeout(()=>{
    console.log("Timer ends")
    },0)

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

});

console.log("sync operation")

You may think its very easy, first console.log() will be executed as its sync operation. Then as there is no timer function so callback of fs.readFile() will be executed.

Inside this callback first console.log() will be executed then according to sequence first Timer's callback will be executed then setImmediate(). Ok let us see if you are right?

Step 1 - console.log() will execute first

sync operation

Step 2 - Poll phase will be executed now, we have a sync operation and 2 async operations inside fs.readFile() callback. Thus first sync operation will be executed !

sync operation 
file reading ended

During this callback execution, two new asynchronous tasks are registered:

• setTimeout() → Timers phase
• setImmediate() → Check phase

Step 3 is very important here

Step 3 - Event Loop moves to the Check phase

At this moment the Event Loop is still in the Poll phase, and the next phase in the loop is the Check phase.

Since setImmediate() belongs to the Check phase, its callback executes next.

Now the output becomes:

sync operation
file reading ended
Immediate ends

Step 4 - Again back to Timers phase

After the Check phase completes, the Event Loop starts the next iteration and enters the Timers phase.

Now the callback of setTimeout() is executed.

Final output:

sync operation
file reading ended
Immediate ends
Timer ends