Skip to main content

Command Palette

Search for a command to run...

Execution Context & Call Stack: How JavaScript code is executed

Updated
6 min read
Execution Context & Call Stack: How JavaScript code is executed
M

Frontend Developer based in India. Working with JavaScript & Figma. Crafting pixel-perfect, secure and performant web experiences with a great attention to detail.

Introduction

JavaScript works quite differently than other programming languages like C, Python or Java. Execution Context & Call Stack are one of the most important building blocks that determine how a JavaScript program runs internally. So, let’s explore these concepts block-by-block, pun intended.

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

First of all, in simple terms, we can divide the execution of a JavaScript program into two main phases:

  1. Creation phase: The JavaScript engine scans the code & sets up references for all variables & functions in memory.

  2. Execution phase: The engine then goes through the code line by line, executing each statement.

Execution Context & Creation Phase

Let's start by understanding the Execution Context (EC). In simple terms, an EC is the environment where a block of JavaScript code runs. This environment includes everything the code needs, such as functions, variables, objects, scopes, etc. There are two main types of ECs:

  1. Global Execution context (GEC): This is the default EC. This is created when a JavaScript program first loads. Any code that is not inside a function is part of this context. There can only be one GEC for each script.

  2. Function Execution Context (FEC): This EC is part of the GEC, but it is only created when a function is called. It contains all the variables & functions defined within that particular function. There can be multiple FECs, depending on how many functions are called. FECs have access to variables & functions in the GEC through the scope chain - a topic for another day, but the reverse is not true, the GEC cannot access variables or functions defined inside FECs. Additionally, FECs can be nested within other FECs if functions are called inside other functions.

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, so there’s nothing to compile! But even with no code, JavaScript still creates an EC called the GEC. This GEC includes default objects like window & document in the browser environment. Now, let’s make things a bit more interesting.

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 is 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 later.

    • A function hello() will be added to the memory & 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. At this stage, only references to the variables & functions in the memory are created. Below is a tabular representation of the current state of the compiling process.

Call Stack & Execution Phase

Let's break down this phase step-by-step using the previous example:

  1. The compiler moves to the first line & assigns "Hello" to the memory reference of a. At this point, a is no longer undefined. The table below shows the current state of the compilation process.

  2. Now, the compiler moves to the second line & sees a function definition. Since the function is already stored in memory during the creation phase, the compiler simply moves to the next line, continuing this process until it encounters an executable expression.

  3. Now, the compiler reaches the line where the function hello() is invoked. It then executes the function code stored in memory & discovers that there is a variable b inside the function. Notice that in the previous steps, I didn’t mention adding b to the GEC. This is because whenever a function is called, a new EC is created specifically for that function - this is called the FEC & it is nested inside the GEC.

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

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

  4. Once the compiler finishes processing the function, it enters the execution phase for the FEC. Here, it assigns the value "World" to b & prints the value of a. Notice that a is not part of the FEC but is defined in the GEC. Still, the function can access a because every child EC has access to it’s parent EC through the scope chain. However, the reverse is not true - the GEC cannot access variables inside the FEC. The following table represents the current state of the compilation process.

  5. Now, the compiler reaches the end of the program & removes the FEC since it is no longer needed. Control then returns to the GEC. With nothing left to execute in the GEC, it is also deleted. This marks the end of the compilation process.

This was a very simple example, but in real-world scenarios, programs often contain multiple nested functions, classes & variables. Keeping track of all the EC created can become quite challenging. To manage this, the JavaScript engine uses a stack to keep track of all the ECs in the program. This is known as the Call Stack.

A Call Stack is a mechanism that helps the JavaScript engine keep track of it’s position in a program that involves multiple nested function calls. Every time a function is invoked, a new EC is created & pushed onto the Call Stack. When a function finishes executing, it’s EC is popped off the stack & control returns to the previous context. This stack-based approach allows JavaScript to manage the order & flow of execution efficiently, even in complex programs with deeply nested functions. The table below illustrates how the Call Stack manages ECs for the program above.

Undefined

During the creation phase, every variable is given a placeholder value of undefined. This is different from "not defined," which means the variable does not exist in memory at all. Undefined simply indicates that the variable has been declared in the program, but no value has been assigned to it yet.

Summary

  1. Every JavaScript program is compiled in two phases: Creation & Execution.

  2. EC is the environment in which a JavaScript code block runs & there are two types:

    • Global Execution Context (GEC): This is the default EC for a JavaScript program & contains all other ECs.

    • Function Execution Context (FEC): Each function has it’s own EC, separate from the GEC. This context holds references to all variables & functions defined within the function & may also contain other FECs for nested functions

  3. The Call Stack is a mechanism that tracks the compiler’s position in a script with multiple functions. Whenever a new EC is created, it is pushed onto the stack. When execution for that context finishes, it is popped off the stack.

  4. During the creation phase, variables are assigned a placeholder value of undefined, which is different from "not defined".

More from this blog

M

Manmohan Singh's Blog

6 posts

Engineering @ PayU · Building things for the Web