Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 27

Execution Contexts and Scope Chains in JavaScript

Recommended resources

Hello! Welcome back.

In our last lesson, we explored how var, let, and const differ in terms of hoisting, scoping, and the Temporal Dead Zone. We concluded that these behaviors are not arbitrary rules but direct consequences of how the JavaScript engine's Execution Context works.

Today, we will dive deep into that fundamental mechanism. This lesson is central to understanding not just variable declarations, but also scope, closures, and the this keyword. Your goal is to be able to describe how the JavaScript engine creates execution contexts and manages the scope chain during code execution. By the end, you'll have a robust mental model of what happens every time you run a piece of JavaScript code.

What is an Execution Context?

At its core, an execution context is an abstract environment where JavaScript code is evaluated and executed. Whenever any code is run in JavaScript, it's done inside an execution context.

There are two main types:

  1. Global Execution Context (GEC): The default context. It's created when a script first loads. There's only one GEC in a program.
  2. Function Execution Context (FEC): Created each time a function is invoked (called).

Every execution context is created in two phases, which we touched upon in the last lesson:

  • Creation Phase: The engine scans the code, sets up memory for variables and functions, and establishes scope links. No code is executed yet.
  • Execution Phase: The engine runs the code line by line, performing assignments and executing function calls.

JavaScript Visualized - Execution Contexts

To start, let's get a precise, technical definition of what an execution context is and see how these two phases work. This video by Lydia Hallie provides a fantastic, visually-driven explanation that uses terminology from the official ECMAScript specification.

Watch the first 1 minute and 16 seconds (00:00 - 01:16). Focus on: The definition of an execution context as an environment for code execution. The distinction between the creation phase (memory setup) and the execution phase (code execution).


The Anatomy of an Execution Context

Now, let's break down what the engine actually creates during the creation phase. Conceptually, an execution context consists of several components, but we'll focus on the two most critical for understanding scope: the Lexical Environment and the Variable Environment.

This diagram provides a high-level overview of the components and their relationships, which we will now explore in detail.

JavaScript Execution Context and Lexical Environment Structure
This diagram illustrates the internal structure of an Execution Context. It shows that an Execution Context holds a `ThisBinding` and references to a Lexical Environment and a Variable Environment. These environments, in turn, point to an Environment Record, which is where variable and function bindings are stored.

JavaScript Visualized - Execution Contexts

The video you just started provides an excellent, detailed breakdown of these internal components. It distinguishes how let/const declarations are handled versus var and function declarations, which directly relates to our previous lesson.

Continue watching from 01:16 to 06:15. This is a dense but crucial section. Pay close attention to: Lexical Environment: The structure that holds identifier-variable mappings. This is where let, const, and function declarations are stored. Variable Environment: A type of Lexical Environment that only handles var declarations. Environment Record: The specific component that stores the bindings. It has two main types: Declarative Record: Stores let, const, class, and function declarations. Object Record: Wraps an object (in the GEC, this is the window object) to provide bindings. This is why var declarations at the global level become properties of window. Outer Environment Reference ([[OuterEnv]]): A reference to the parent's lexical environment. This is the key to the scope chain.

As the video explained, the separation of Lexical and Variable Environments is how the engine implements the different behaviors of let/const versus var. In modern JavaScript (ES6+), for a given scope, both environments often point to the same underlying components, but the distinction is useful for understanding the specification.


Managing Contexts: The Call Stack

So, the engine creates a new Function Execution Context every time a function is called. How does it keep track of them, especially with nested calls? It uses a Call Stack (also known as the Execution Stack).

The Call Stack is a data structure that operates on a LIFO (Last-In, First-Out) basis:

  1. When a script loads, the Global Execution Context is pushed onto the stack.
  2. When a function is called, a new Function Execution Context is created and pushed onto the top of the stack, becoming the active context.
  3. If that function calls another function, a new FEC is pushed on top of it.
  4. When a function completes (i.e., hits a return statement or the end of its code), its FEC is popped off the stack, and control returns to the context below it.

Master JavaScript Execution Context — Hoisting, Scope & Closures Made Clear

Let's visualize how the call stack works. This video provides a clear, step-by-step animation of contexts being pushed and popped.

Watch from 22:15 to 27:30. This segment first introduces the concept of the call stack and then walks through a simple nested function example, showing how the global, one(), and two() execution contexts are stacked and unstacked.


The Scope Chain in Action

Now we can connect all the pieces. The Scope Chain is the mechanism the engine uses to find the value of a variable. It's nothing more than the chain of lexical environments, linked together by their outer environment references.

When you reference a variable, the engine performs the following lookup:

  1. It starts in the Environment Record of the current execution context's Lexical Environment.
  2. If it doesn't find the variable there, it follows the [[OuterEnv]] reference to the parent's Lexical Environment.
  3. It repeats this process, moving up the chain of parent scopes.
  4. This continues until it finds the variable or reaches the Global Execution Context. If it's still not found, a ReferenceError is thrown.

This chain is lexically defined, meaning it depends on where the functions are written in the source code, not where they are called from.

This diagram shows a visual representation of this linked chain.

JavaScript Lexical Environments and Environment Records
This diagram shows a chain of environments. The `[[OuterEnv]]` reference (blue arrows) from each lexical environment points to its parent, forming the scope chain that the JavaScript engine traverses to resolve variable names.

Let's read a clear explanation of this lookup process.

Everything I Wish I Knew About Javascript Scoping A Week Ago

This article provides a superb explanation of how the call stack and scope chain work together, and clarifies the crucial distinction between static (lexical) scoping and dynamic scoping.

Read the sections 'Execution Contexts' and 'Static vs Dynamic Scoping'. In 'Execution Contexts', focus on the diagram and the accompanying text that describes how the engine searches for variables a, b, c, and d by traversing the lexical environments from inner to outer to global. The 'Static vs Dynamic Scoping' section is key for conceptual understanding. It explains why the scope chain follows the lexical structure of your code, contrasting it with the behavior in a dynamically scoped language like Bash.

The concept of static (lexical) scoping is fundamental. It's what allows us to reason about our code just by reading it, without having to trace every possible way a function could be called. The [[OuterEnv]] of a function's environment is set once, during the function's creation, and it points to the environment where the function was defined.


Conclusion

You now have a detailed model of how the JavaScript engine executes code. This is one of the most important "under-the-hood" concepts in JavaScript, and understanding it will clarify many other topics we'll cover.

Key Takeaways:

  • Execution Contexts (ECs) are the environments where JavaScript code runs. The main types are Global and Function.
  • ECs are managed on the Call Stack using a LIFO (Last-In, First-Out) principle.
  • Each EC has a Creation Phase (memory setup) and an Execution Phase (code execution).
  • During creation, the engine creates Lexical and Variable Environments, which contain Environment Records to store variable and function bindings.
  • The [[OuterEnv]] property of a Lexical Environment points to its parent's environment, forming the Scope Chain.
  • JavaScript uses static (lexical) scoping, meaning the scope chain is determined by where functions are defined in the code, not where they are called.

What's Next?

We've seen how the engine allocates memory for variables within execution contexts. But what happens to that memory when it's no longer needed? In our next lesson, we will explore the high-level process of memory allocation and garbage collection in JavaScript, a crucial topic for writing performant and memory-efficient applications.