Understanding NodeJS Event Loop Model

Satheesh
Insights from ThoughtClan
3 min readSep 13, 2022

--

I have always wondered how NodeJS server runtime is able to handle high throughput applications, when it is said that NodeJS is single-threaded 🤔. So, I decided to dig a little deeper to understand how the runtime makes this possible.

Overview

First, let’s look at what NodeJS is. It is a JavaScript runtime built to run on the server side. It is built on top of Google’s open-source V8 high-performance JavaScript engine and is fully ES6 (ECMAScript language syntax) compliant.

NodeJS can be used in a variety of use cases including but not limited to:

  • Web applications,
  • Cloud serverless applications,
  • Scripting,
  • Build management.

Below is a quote from Node JS’s about page. As it can be understood from this statement, event-driven programming is at the core of NodeJS runtime.

As an asynchronous event-driven JavaScript runtime, Node.js is designed to build scalable network applications.

(https://nodejs.org/en/about/)

Is NodeJS Single-threaded?

While the business logic that we write and the event loop runs in a single thread, NodeJS hands-off time-consuming tasks (including I/O) to an underlying library called Libuv which delegates such tasks to the kernel or a thread pool. So, to answer this question, NodeJS is not single-threaded.

Single-Threaded Event Loop Model

Node JS uses a concept called “Single-Threaded Event Loop Model” designed for asynchronous non-blocking I/O. This principle is heavily dependent upon queues & callbacks.

  • When we start a NodeJS application, the single-threaded runtime starts the ”main” script execution and the “event loop”.
    - In case of scripting, this would be the start-up method e.g., logic that is written in index.js.
    - In case of web applications, this would be web server initialisation logic e.g., express server.
  • Any I/O operations are automatically queued.
    - This includes incoming HTTP requests too.
  • Such operations are delegated to Libuv (underlying C library).

This principle is somewhat similar to reactive programming based web application architectures like e.g., Vert.x, Spring Webflux etc.

How It Works

Below is a representation of how NodeJS handles time-consuming / I/O operations by handing-off to Libuv.

NodeJS Event Loop Model

When an HTTP request comes in,

  • Libuv delegates the task of reading the request from the socket to the kernel. Once request data becomes available, an event is posted in the Event Queue.
  • The Main Thread picks up the event from the queue and starts processing it (handles the request).
  • As part of the request handling, if there are any I/O operations, control passes off to Libuv.
  • Such operations are in-turn handled in two different ways:
    - Some operations (e.g., making / receiving TCP calls) are handled directly by the kernel.
    - Some operations (e.g., reading / writing files) are handled by a thread pool maintained by Libuv.
  • In parallel, Main Thread is now free to pick next event from the event queue.
  • Once operation is complete, event is pushed into the Event Queue.
  • Main Thread will pick and process the event at the top of the Event Queue as and when it becomes free.

Summing Up

As we can see, NodeJS is not single-threaded. Even though user code and event loop run in a single thread, all I/O operations are delegated either to the kernel or to a thread pool.

One final note for NodeJS newbies before we part. Do not use the “Sync” suffixed methods provided by some libraries e.g., fs.readFileSync(); this blocks the Main Thread. Always use async methods; prefer to return the promise wherever possible; use “await” wherever the response from the async method is required to continue executing the logic.

--

--