Starting in 2014, I wrote a series of articles focused on JavaScript, highlighting key features through examples.

I later expanded upon these articles by including the new features enabled in ECMAScript 2015 (AKA ES6).

Although understanding the language itself is clearly important, it is also critical to understand how the different runtime engines execute the code.

As a result, this article will aim to demystify the architecture of Node.js.

The diagram below highlights the key components (Call Stack, Node APIs, Callback Queue and Event Loop), and will be referenced throughout the article.

Node Architecture

To help explain the architecture, I will highlight a synchronous and asynchronous example.

Synchronous Example

The Call Stack is a simple data structure that keeps track of program execution, including current functions and statements. When interacting with the Call Stack, you can either add an item to the top or remove the top item. A simple analogy would be a can of Pringles, where you can only add or remove the top Pringle.

The following code would execute fully within the Call Stack (not interacting with the Node APIs, Callback Queue and Event Loop):

var x = 1;
var y = x + 1;
console.log('y is ${y}');


All of these statements would be executed in order, on top of the ‘main()’ function, which is always executed first.

Asynchronous Example

The following code is asynchronous and would leverage the Call Stack, Node APIs, Callback Queue and Event Loop.

console.log('Launch App');

setTimeout(() => {
    console.log('First Timeout');
}, 4000);

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

console.log(Close App');


The first ‘console.log’ statement runs immediately on top of the ‘main()’ function within the Call Stack.

The statement ‘setTimeout’ is a Node API, which unsurprisingly gets registered under Node APIs (see diagram above), storing the four-second delay.

At this point, the program continues to progress, moving to the next statement (Second Timeout), while the previous ‘setTimeout’ continues to countdown. This statement is calling the same Node API, but with a zero second delay.

Assuming the Second Timeout finishes first, it will not execute straight away, instead, it will move to the Callback Queue. The Callback Queue includes all of the callback functions that are ready to be executed.

The Callback Queue has to wait for the Call Stack to be empty (including the ‘main()’ function), at which point the first function can be executed. If there are multiple functions in the Callback Queue, they will be executed in order. It is the Event Loop which monitors the Call Stack, waiting for it to become empty.

In the code example, the order of execution will be:

  1. Close App
  2. Second Timeout
  3. First Timeout

This is because the Node APIs cannot execute until the Call Stack is empty. At which point, the Second Statement is actually executed first, as it had a short delay (zero seconds).

Conclusion

It is important to recognise that as a programmer, you never directly interact with the Call Stack, Node APIs, Callback Queue and Event Loop. However, the mechanics can have a profound impact on the way your application executes, therefore a basic understanding is critical.