Alex Delgado

From Good to Great: True Engineers Understand JavaScript Inside Out

April 17, 2023

To write high-quality code, it’s essential to comprehend how that code runs.

Always bear in mind: “Your coding can only be as good as your understanding.”

JavaScript, which holds a significant presence in the global programming world, functions based on a unique execution model.

[object Object]JS Runtime Environment Diagram created by Yuri Bett

Let's understand each part of this runtime environment.

JAVASCRIPT'S EXECUTION ENVIRONMENT

The execution environment, frequently referred to as the JavaScript engine, is a complex space where all the magic of running your JavaScript code happens. Various browsers and platforms have their specialized engines: V8 powers Google Chrome and Node.js; SpiderMonkey is behind Firefox; JavaScriptCore runs inside Safari.

Within this execution environment, two primary components play important roles in how your code gets executed: the Heap and the Call Stack.

The Heap

The Call Stack

For illustrative purposes:

function multiply(x, y) {
  return x * y;
}

function calculate() {
  const value = multiply(5, 3);
  console.log(value);
}

calculate();

In this example:

  1. The calculate() function is called, placing its frame on the Call Stack.
  2. Inside calculate(), the multiply() function is invoked, adding its frame to the top of the stack.
  3. Once multiply() completes, it returns the value 15, and its frame is removed from the Call Stack.
  4. The calculate() function continues its execution and logs the value 15 to the console. After it finishes executing, its frame is also popped off the Call Stack.
[object Object]How JS Runtime Environment works with the example code

JAVASCRIPT'S ASYNCHRONOUS MECHANICS

In a synchronous world, each instruction must wait for the previous one to complete. However, to deal with operations that might take unpredictable amounts of time (like reading a file or fetching data from a server), JavaScript employs a non-blocking, asynchronous model. This model is made efficient through a combination of Web APIs, the Callback Queue, and the Event Loop.

Web APIs

Callback Queue

Event Loop

Consider the following code fragment:

console.log("First");
setTimeout(function () {
  console.log("Second");
}, 0);
console.log("Third");

Let's try to find out its output:

  1. console.log('First') is added to the Call Stack and executed.
  2. setTimeout() is encountered. The timer operation is handed over to the Web APIs.
  3. Immediately after, console.log('Third') is added to the Call Stack and executed.
  4. Even though the timer duration is 0 milliseconds, the callback of setTimeout() (which logs 'Second') is placed in the Callback Queue.
  5. The Event Loop, noticing the Call Stack is empty and there’s a function in the Callback Queue, transfers the callback to the Call Stack.
  6. Finally, console.log('Second') is executed.

Then the output of the code will be:

First
Third
Second

This entire mechanism ensures that even for asynchronous code, the program execution flow remains consistent and non-blocking.

Check how the diagram illustrates this case:

[object Object]How Event Loop works Diagram

The Microtask Queue

In order to understand, consider this example:

console.log("Start");

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

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

console.log("End");

The output would be:

Start
End
Promise
setTimeout

This order is because, after executing the synchronous code (Start and End), the event loop sees there’s a promise in the microtask queue and executes that before the setTimeout() in the task queue, even though the setTimeout() has a delay of 0.

Let's look at the diagram below. Note that the Promise is added by the Javascript Engine now since it is a direct promise, not created by fetch, that case it would go first through Web API.

[object Object]How Microtask Queue works Diagram

FINAL ADVICE

As we wrap up, here’s something I would have liked to know when I started working in the world of programming and, in particular, with Javascript:

Being a great developer isn’t just about writing code. It’s about really understanding how that code works.

When you get how things like the Event Loop, Microtask Queue, and Call Stack work, you’ll feel more connected to your code. Every piece of code you write will make more sense because you know what’s happening behind the scenes.