Execution Context & Call Stack: How JavaScript code is executed

Introduction

JavaScript works very differently than other programming languages like C or Python or Java. Execution Context & Call Stack are one of the most important building blocks of how a JavaScript program runs internally. So let's understand everything block-by-block, pun intended.

Note: For this article, I am assuming you've basic knowledge of JavaScript.

First of all, in layman's terms, let's just say that we can divide the compiling of a JavaScript program in 2 phases:

  1. Creation Phase: A reference to all the variables & functions in the memory is created.

  2. Execution Phase: The JavaScript engine then starts going line-by-line through our code & it executes every single line.

Execution Context & Creation Phase

Let's get started by understanding Execution Context (EC). Execution Context is nothing but the environment in which a JavaScript code block is executed. By environment, I mean everything - functions, variables, objects, etc. There are 2 types of ECs:

  1. Global Execution context (GEC): This is the default EC in which the JavaScript program is first loaded. Anything which is outside a function is a part of this EC. There can be only 1 GEC.

  2. Function Execution Context (FEC): This EC is a part of GEC but it is only created when a function is called. It contains all the variables & functions defined inside that function. There can multiple FECs depending on the number of functions. FECs also have the access to the variables & functions defined in the GEC. But reverse is not true. GECs don't have the access to the FECs. There can be FECs inside FECs if 2 or more functions are nested.

Execution Context is nothing but the environment in which a JavaScript code block is executed.

Let's understand this with a simple example of The Smallest JavaScript Program.

// The Smallest JavaScript Program

You might be wondering this is an empty program, there is nothing to compile! But still JavaScript creates an EC while running the above program known as Global Execution Context (GEC). This GEC consists of simple pre-defined variables such as window, document, etc. Now let's complicate things a little bit.

var a = "Hello";

function hello() {
  var b = "World";
  console.log(a, b);
}

hello(); // Hello World

Let's go step-by-step & see what happens in the above program:

  1. GEC will be created with all the pre-defined values such as window, document, etc.

  2. Now all the variables & functions declared in the code will be added to the GEC, i.e. a reference to these variables & functions in the memory will be created.

    • A variable a will be added to the memory, but it will be undefined instead of "Hello", explained at the end.

    • A function hello() will be added to the memory, & now incase of functions it will store the whole function definition, i.e. the whole function code instead of undefined.

  3. Now the program will move to the Execution Phase. This is where Call Stack comes into the picture.

Note: The program has not started compiling yet. Only references to the variables & functions in the memory are created. Below is a tabular representation of the current state of the compiling process.

image.png

Call Stack & Execution Phase

Let's understand this phase by going step-by-step using the last code example:

  1. The compiler will move to the 1st line & assigns "Hello" to the memory reference of the a. Now it is not undefined. Following table represents the current state of the compiling process.

  2. Now the compiler moves to the 2nd line & it sees that it is a function definition which is already there in the memory so it moves to next line until it finds some expression.

  3. Now the compiler reaches the line where function hello() is invoked & it goes through the function code stored in the memory & finds out there is a variable b in the function. Remember in the previous 3 steps, I did not talk about adding b to GEC. This is because, whenever there is a function in the program, a new EC is created for functions which is nested inside the GEC & it is known as Function Execution Context (FEC).

    • Now a new FEC will be created for the function hello().

    • In this FEC b will be added & it will undefined. Following table represents the current state of the compiling process.

      image.png

    • Now the compiler goes through next lines of functions to see if there are any other variables or functions to add to the FEC.

  4. Once the compiler is finished going through the function it moves to the execution phase for this FEC which will include assigning the value "World" to b & printing the value of a. But now, if you notice that a is not a part of FEC but it is a part of GEC but still, it will print a as FEC has access to GEC, i.e. every child EC will have access to it's parent EC. Following table represents the current state of the compiling process.

  5. Now the compiler moves to the end of the program & it deletes the FEC as it is no longer required & moves back to GEC & there is nothing to be compiled in GEC as well so it deletes the GEC. This marks the end of the compiling process.

This was a very simple example but in real life, a program consists of multiple nested functions, classes & variables. It will be very hard to keep a track of all the ECs created. To manage this JavaScript engine has a stack of all the ECs in the program. This called the Call Stack.

A Call Stack is a mechanism for the compiler to keep track of it's place in a program that calls multiple nested functions. ECs' Call Stack mechanism for the above program is given below.

image.png

A Call Stack is a mechanism for the compiler to keep track of it's place in a program that calls multiple nested functions.

Undefined

During creation phase every variable is given a placeholder value of undefined. This is different than not defined. It is just a placeholder that describes that the variable is declared in the program but the value is still not assigned to the variable.

Summary

  1. Every JavaScript program compilation consists of 2 phases - Creation & Execution.

  2. Execution context is an environment in which a JavaScript code block runs & it is of 2 types:

    • Global Execution Context: It is the default EC for a JavaScript program & it contains all the other ECs.

    • Function Execution Context: Every function has it's own EC which is different than the GEC. This EC consists reference to all the variables & functions defined in a function. It also contains other FECs for the functions defined in the parent function.

  3. Call Stack is a mechanism to keep track of the position of the compiler in a script with multiple functions. Whenever a new EC is created it is pushed to the stack & when it is finished executing the code for that particular EC, it is popped out of the stack.

  4. During the creation phase a placeholder value of undefined is assigned to variables. This is different than not defined.