DEV Community

Cover image for 👀Understanding the JavaScript Call Stack: How Your Code Really Runs 🚀
Adrian Guillen
Adrian Guillen

Posted on • Edited on

👀Understanding the JavaScript Call Stack: How Your Code Really Runs 🚀

How the JavaScript call stack works is something that every frontend developer has asked at least once in his career, and in my opinion, it's not a question that has been answered in most places, and the answers are not always clear or easy to understand. That's why I've decided to cover the topic in this post.

Let's start from the beginning. The JavaScript engine runs the code line by line synchronously, and every time a function is executed, it creates an execution context (a space in memory to store all the scoped properties that exist only inside that function) and adds the function to the call stack.

JavaScript only executes the code of the function that is on the top of the stack, and when a function finalises and returns its value, the engine removes the function from the call stack and starts working on the next one.

When the call stack is empty, the JavaScript engine continues running the code in the next global context, or what is the same, continues executing the lines that are in the root of the JavaScript file and don't belong to any function.

Let's see some examples, line by line:

const num1 = 2;
const num2 = 5;

function sum(a, b){
return a + b;
}

const result= sum(num1, num2);
console.log(result) // 7

Enter fullscreen mode Exit fullscreen mode

This is a really simple code that defines 2 constants (num1 and num2) and then defines a function sum that sums 2 numbers and returns the result of the sum. Finally, the constant result is created, and the result of executing sum with the arguments num1 and num2 is assigned to it. Then the value of the result is printed on the console.

If you think that the previous explanation is too simple or too complex and does not explain anything, please bear with me, we are getting to the interesting stuff.

Let's see what the JavaScript engine is doing, line by line. In the first line, the engine creates a label num1 and stores in memory the value 2.

const num1 = 2;
Enter fullscreen mode Exit fullscreen mode

The second line does the same for label num2. It creates a label num2 and stores in memory the value 5.

const num2 = 5;
Enter fullscreen mode Exit fullscreen mode

What this means is that whenever someone inside the global scope requires the value of num2 the engine is going to change the label and put the value 5 instead.

Let's continue with the next line. The next line is the function sum. What do you think the engine is going to do? Do you think it is going to execute the function or add it to the call stack? Nope! What the engine is going to do is store a new label named sum and store the code inside the brackets in memory. Or what is the same, It's going to store the function definition and assign it to the sum label.

function sum(a, b){
return a + b;
}
Enter fullscreen mode Exit fullscreen mode

if we could visually see the memory of the code that we've run so far, we would see something like this:

Label name Value in memory
num1 2
num2 5
sum function definition

The next line is a really interesting one. When the JavaScript engine reaches the next line, it creates the label result, but at this point, it doesn't yet know what value needs to be assigned to the label because the value is the result of executing a function, so first, it needs to execute the function, do whatever the function needs to do, and get the result from the return value.

const result= sum(num1, num2);
Enter fullscreen mode Exit fullscreen mode

At this point, the engine adds the function to the call stack and also creates a new execution context, which is a new space in memory were JavaScript can store the value of the args and every other property that is inside of the function without colliding with the global context.

Call stack
sum
Global

First of all, the engine creates in memory the labels a and b which are the names given to the parameters, and it assigns the value of the arguments 2 and 5 respectively.

If we could see the memory at this specific moment, we would see something like this:

Label name Value in memory
a 2
b 5
return 2 + 5 = 7

In this case, the function is really simple and only returns the value of the sum between a and b, so the engine substitutes the parameters with the values of the arguments and returns the value to the global execution context. Finally, the function is removed from the call stack, and only the global context remains.

Call stack
Global

At this point, the result of the function is assigned to the label result and we can print the value on console with the console log.

Let's take a look at how the global memory looks now:

Label name Value in memory
num1 2
num2 5
sum function definition
result 7

Did you noticed? the label result has a value of 7? and also sum still has the function definition inside.

Let's take a look at the next code:

const num1 = 2;
const num2 = 5;

function sum(a, b){
return a + b;
}

const result= sum(num1, num2);
console.log(result) // 7

function sumThreeNumbers = (x,y,z) => {
return sum(x, y) + z
}

const result2 = sumThreeNumbers(4,6,2)
console.log(result2) // 12

Enter fullscreen mode Exit fullscreen mode

The main difference is that now we have a new sumThreeNumbers function and we are creating a new result2 constant and assigning the value of running the function sumThreeNumbers with the arguments, 4, 6 and 2.

Let's take a look at how the call stack works when we run nested functions.

If we jump to the line when we define the constant result2 the global memory would look something like this:

Label name Value in memory
num1 2
num2 5
sum function definition
result 7
sumThreeNumbers function definition

Just as on the previous example, the JavaScript engine doesn't know what value to assign to the label result2, to get the value, first needs to execute the function sumThreeNumbers with the arguments. The function is added to the call stack, and a new execution context is created. The execution context would look like this:

Label name Value in memory
x 4
y 6
z 2

So the first thing that JavaScript does is create the parameter labels and assign the value provided by the arguments.

Now let's take a look at our call stack

Call stack
sumThreeNumbers
Global

As you can see, the call stack only has the sumThreeNumbers item (apart from the global context that is always present).

To be able to get the result value, the function sum needs to be executed first, so the engine will add the function to the call stack and create a new execution context for the sum function.

Call stack
sum
sumThreeNumbers
Global

As the sum function is on top of the call stack, Javascript needs to run sum first to be able to continue running sumThreeNumbers.

This is how it's going to look the execution context for the function sum:

Label name Value in memory
a 4
b 6
return 4 + 6 = 10

As you know, it will return _10 _and it will be removed from the call stack

Call stack
sumThreeNumbers
Global

The JavaScript is going to continue executing the sumThreeNumbers and the execution context will look like this:

Label name Value in memory
x 4
y 6
z 2
return 10 + 2 = 12

It will return the value 12 and be removed from the call stack.

Call stack
Global

Then the value 12 will be assigned to the property result2 and the value 12 will be displayed in the console.

I hope this post has help you to understand how the JavaScript call stack works, if so please leave a comment and a like. I see you in the next one!

Top comments (0)