The Event Loop 🔄️

Hi, everyone 👋

In this post, which is part of my next book, I'd like to talk about the event loop, and how it works in Node.js.

The Event Loop

The NodeJS event loop is a critical component of the NodeJS runtime that enables asynchronous, non-blocking I/O operations. The event loop is responsible for managing the execution of the application's code and ensuring that processes are executed promptly and efficiently.

At a high level, the event loop operates by continuously monitoring a queue of events and executing any associated callback functions as soon as possible. Events can be generated by various sources, such as user input, network requests, timers, or file I/O operations.

When an event is generated, it is placed in the event queue, along with any associated callback functions that should be executed when the event is processed. The event loop then enters a loop that continuously checks the event queue, and executes any callbacks associated with the next event in the queue.

If the event queue is empty, the event loop waits for new events to be generated, while still allowing the application to continue executing other code. This is what allows NodeJS to handle multiple asynchronous operations simultaneously, without blocking the execution of other parts of the application.

One important thing to note is that callback functions associated with I/O operations are executed asynchronously, meaning that they are executed outside of the main program flow. As a result, the order in which callbacks are executed can be unpredictable, depending on the order in which the associated events are processed.

The NodeJS event loop consists of several phases, each responsible for processing specific events in a specific order. Understanding the phases of the event loop is crucial for building efficient, performant NodeJS applications. Here are the different phases of the NodeJS event loop:

  1. Timers Phase: In this phase, any scheduled setTimeout() and setInterval() callbacks are executed.
  2. Pending Callbacks Phase: In this phase, any I/O callbacks deferred from the previous loop iteration are executed.
  3. Idle, Prepare Phase: This phase is used internally by NodeJS to prepare for the next loop iteration.
  4. Poll Phase: This is the most important phase of the event loop. In this phase, the event loop will wait for I/O events to complete and will execute their associated callbacks as soon as they are ready. The poll phase is also responsible for detecting and responding to system events such as incoming network requests or file I/O operations.
  5. Check Phase: In this phase, any setImmediate() callbacks are executed immediately after the poll phase.
  6. Close Callbacks Phase: In this final phase, any "close" events are handled, such as when a socket or server is closed.

It's important to note that each phase of the event loop has an associated queue. When a new event is generated, it is placed in the appropriate queue for its phase. Once the current phase has completed processing all events in its queue, the event loop will move on to the next phase and process its queue.

The order in which the phases are executed can vary depending on the specific circumstances of the application. For example, if no timers are scheduled, the event loop may skip the timers phase entirely and move directly to the poll phase. Similarly, if there are no I/O callbacks deferred from the previous loop iteration, the pending callbacks phase may be skipped.

Overall, the event loop in NodeJS is a powerful and efficient mechanism for handling asynchronous I/O operations. By understanding the different phases of the event loop and how they operate, developers can build more efficient and scalable NodeJS applications.

To illustrate how the event loop works in practice, let's look at an example:

// app.mjs
setTimeout(() => {
  console.log('Timeout 1')
}, 1000)

setTimeout(() => {
  console.log('Timeout 2')
}, 500)

console.log('Application started')

In this example, we use the setTimeout function to register two timers with the event loop. The first timer will execute its callback function after 1 second, while the second timer will execute its callback function after only 500 milliseconds. We also print a message to the console indicating that the application has started.

When we run this code with node app.mjs, we see the following output:

Application started
Timeout 2
Timeout 1

Notice that the "Application started" message is printed first, followed by the "Timeout 2" message after 500 milliseconds, and then the "Timeout 1" message after 1 second. This order is determined by the order in which the timers expire and their associated callbacks are executed by the event loop.

This example demonstrates the power and flexibility of the NodeJS event loop. By enabling non-blocking, asynchronous I/O operations, NodeJS allows for highly efficient and scalable application development.

Another important feature of the event loop is its ability to handle I/O operations in a non-blocking manner. Let's look at another example that demonstrates this:

import { readFile } from 'node:fs/promises'

console.log('Reading file...')
readFile('./example.txt')
  .then((data) => {
    console.log(`File contents: ${data}`)
  })
  .catch((error) => {
    console.error(`Error reading file: ${error}`)
  })

In this example, we use the readFile function from the built-in fs module to read the contents of a file. The readFile() function returns a promise, which we handle using .then and .catch functions.

When we run this code with node app.mjs, we see the following output:

Reading file...

This output is printed immediately, indicating that the file read operation has been registered with the event loop, but the callback function has not yet been executed. As soon as the file contents have been read, the event loop executes the callback function, which prints the contents of the file to the console.

These are just a few examples of how the NodeJS event loop works. By using the event loop to handle asynchronous operations in a non-blocking manner, NodeJS allows for efficient and scalable application development.


If you liked this introductory post about The Event Loop in NodeJS, or you would like to start learning JavaScript to bootstrap your career, you might be interested in my introductory book to JavaScript 👉 The JavaScript Cooking Book: Learn JavaScript from the ground-up