Skip to main content

Command Palette

Search for a command to run...

Understanding Javascript Execution context

Updated
•5 min read•View as Markdown
Understanding Javascript Execution context

I was going through the MoneyCircle codebase when I encountered a bug that required me to trace a function's execution. It was an easy fix, but it got me thinking about JavaScript execution context and how it works.

This was one of the topics I found interesting when I started learning JavaScript, and I believe anyone seeking to understand how JavaScript executes code should look into it.

In this article, I'll explain JavaScript execution context using practical examples, breaking it down bit by bit.

What Is an Execution Context?

When JavaScript executes code, it uses an environment known as an execution context. This environment keeps track of the code being executed and the information needed to execute it, including variables, function declarations, and other bindings.

JavaScript creates a global execution context to execute top-level code. Whenever a function is invoked, a new function execution context is created for that call.

Execution context creation involves two conceptual phases:

Memory creation phase: Bindings are established and initialized before the code executes. Execution phase: The code is evaluated, and variables are assigned values as their declarations are executed.

Let's look at what happens in each phase.

  1. Memory Creation Phase

Before the code executes, JavaScript prepares the execution context and initializes the relevant bindings.

For example, in a typical browser script, the global environment provides access to the global object through window. In Node.js, you can access the global object through globalThis or the Node.js-specific global reference.

The value of this depends on the execution environment and how the code is executed.

During initialization, JavaScript handles declarations differently depending on whether they use var, let, const, or a function declaration.

For example:

Variables declared with var are initialized to undefined before their assignments execute. Function declarations are initialized with their function objects, allowing the functions to be called before their declarations appear in the source code. Variables declared with let and const are created but cannot be accessed before initialization. This period is known as the temporal dead zone (TDZ).

These differences help explain a concept known as hoisting.

  1. Execution Phase

During the execution phase, JavaScript evaluates the code in order.

When a function is invoked, JavaScript creates a new function execution context. That context handles the function's parameters, local variables, and other bindings.

When the function returns, its execution context is removed from the active execution context stack, and execution resumes in the calling context.

Let's put this into practice.

A Practical Example

In this example, I've created a function that adds two numbers passed to it as arguments. I've also declared two variables, x and y, whose values are passed to the get_sum function. The returned value is stored in a variable called sum.

var x = 2;

var y = 4;

function get_sum(i, j) {

var sum = i + j;

return sum;

}

var sum = get_sum(x, y);

console.log(sum); // 6 When you run this code, the output is 6.

What Happens During Initialization?

Conceptually, the global execution context has the following bindings initialized before the code executes:

var x = undefined;

var y = undefined;

function get_sum(i, j) {

var sum = i + j;

return sum;

}

var sum = undefined;

This is an illustration of the initialization process, not a literal representation of how JavaScript rewrites or stores the source code.

Notice that the function declaration is available as a function, while the variables declared with var are initialized to undefined.

What Happens During Execution?

JavaScript executes the code in order.

First, x is assigned the value 2, and y is assigned the value 4.

Next, JavaScript encounters this line:

var sum = get_sum(x, y);

To evaluate this expression, JavaScript invokes get_sum, creating a new function execution context.

Inside that context, the parameters receive their argument values:

i = 2;

j = 4;

The local variable sum is initialized to undefined and then assigned the result of i + j.

Conceptually, the function execution looks like this:

function get_sum(i, j) {

// Parameters receive the values 2 and 4. var sum = undefined;

// Execution phase sum = i + j; // 6

return sum; // Returns 6

}

The function returns 6, and that value is assigned to the outer sum variable.

Finally, console.log(sum) prints 6.

It is important to understand that the sum variable inside get_sum and the sum variable outside the function are two separate bindings. They share the same name, but they belong to different scopes.

What About let and const?

Consider the following example:

console.log(x); // undefined

var x = 10;

console.log(y); // ReferenceError

let y = 20;

Why does the first statement return undefined, while the second throws an error?

The difference comes from how JavaScript initializes these declarations.

A var binding is initialized to undefined before its assignment executes. A let binding, however, cannot be accessed before its initialization.

The period between entering the scope and initializing a let or const binding is known as the temporal dead zone.

The TDZ is not a separate storage location. It describes a period during which accessing the binding results in a ReferenceError.

Also, var is function-scoped, whereas let and const are block-scoped. In a browser's classic script, a top-level var declaration generally creates a property on the global object, but this does not apply to every var declaration or every JavaScript environment.

Understanding JavaScript execution context has been helpful to me over the years, whether I'm debugging an issue or exploring a new codebase and trying to understand how things work.

I believe anyone learning JavaScript, or even vibe-coding a JavaScript project, should have a solid understanding of execution context. It provides a useful foundation for understanding function calls, scope, hoisting, and how JavaScript executes code.

If you'd like to explore these concepts in more detail, check out the following resources:

. JavaScript execution model — MDN

. Hoisting — MDN