JavaScript Execution Context: A Complete Tutorial

An execution context is the environment JavaScript creates every time it runs code. Learn how global and function contexts, scope chains, and the creation phase work.

7 min read

An execution context is the environment that JavaScript creates every time it runs your code. It holds the variables, functions, and scope rules that a piece of code can access. Whenever a script starts or a function is called, the engine builds a new execution context.

Think of an execution context as the workspace for a piece of running code. It contains everything the code needs: its own variables, a reference to outer scopes, and the value of the this keyword.

Two Kinds of Execution Context

JavaScript creates two types of execution context. The global context is created once when the script first runs and stays alive until the program ends. Function contexts are created every time a function is called and destroyed when the function returns.

The global context is always at the bottom of the call stack. Every function call pushes a new context on top. When the function returns, its context is destroyed and the previous context resumes.

What an Execution Context Contains

Every execution context has three key parts.

Variable Environment

The variable environment stores all local variables and function declarations for that scope. When you declare let count = 0 inside a function, count lives in that function's variable environment. Variables declared with var are stored one way, while let and const are stored separately in the lexical environment, but the concept is the same: the context holds your local variables.

Scope Chain

Every context has a reference to its outer, parent environment. When the engine looks up a variable and cannot find it in the current context, it follows this reference outward. It keeps walking up the chain until it reaches the global context.

This chain of references is the scope chain, and it is what makes closures possible.

The This Binding

Each context sets the value of the this keyword. In the global context, this is the global object (window in browsers, global in Node.js). In a function context, the value of this depends on how the function is called.

The Two Phases: Creation and Execution

Every execution context goes through two distinct phases. This is the key to understanding hoisting and scope behavior.

Execution context lifecycle

The creation phase happens before any of your code runs inside that context. The execution phase is when your actual code runs, line by line.

Creation Phase

Before running a single line, the engine does three things. First, it sets up the scope chain by creating a reference to the outer environment. Second, it creates the variable environment by scanning for variable and function declarations and allocating memory.

Third, it determines what this points to.

During this phase, var variables are initialized to undefined. let and const variables are created but remain uninitialized.

Function declarations are stored in full. This is why you can call a function before its declaration.

Execution Phase

Now your code runs, line by line. Variables get their actual values. Function calls trigger new execution contexts.

Expressions are evaluated. The engine follows the scope chain for every variable lookup.

How the Creation Phase Explains Hoisting

Hoisting is not a feature you enable. It is a side effect of the creation phase.

javascriptjavascript
console.log(name);
console.log(age);
 
var name = "Alex";
let age = 30;

The creation phase scans the code, finds var name and let age, and allocates memory for both. The variable name gets initialized to undefined. The variable age is created but stays uninitialized, in what the spec calls the Temporal Dead Zone.

During the execution phase, logging name prints undefined because the variable exists, just with no real value yet. Logging age throws a ReferenceError because the variable exists but is uninitialized and cannot be accessed.

Function declarations are fully hoisted. You can call a function before its declaration line because the creation phase stores the entire function definition. For more on how variables behave across scopes, see /javascript/global-vs-local-variables-in-javascript-guide.

The Global Execution Context in Detail

When your script starts, the engine creates the global execution context. This context holds all top-level variable declarations, creates the global object (window in browsers), sets this to the global object, and has no outer environment because the scope chain ends here. The global context runs through its creation phase, then its execution phase, and stays alive until the tab closes or the Node.js process exits.

Function Execution Contexts in Detail

Every function call creates a new execution context. Even the same function called twice creates two separate, independent contexts.

javascriptjavascript
function multiply(a, b) {
  const result = a * b;
  return result;
}
 
multiply(3, 4);
multiply(5, 2);

The first call to multiply creates a context with its own a, b, and result. The second call creates a completely separate context. The two contexts do not interfere with each other.

Each gets its own variable environment with its own parameter values.

In non-arrow functions, the creation phase also sets up an arguments object, an array-like structure containing all passed arguments. This is available even when the function declares no named parameters. Arrow functions do not get their own arguments object; they inherit it from their enclosing scope.

How Contexts Interact with the Call Stack

Execution contexts and the call stack work together. The call stack tracks which context is currently active.

Execution contexts on the call stack

Here is code that creates this pattern:

javascriptjavascript
function inner() {
  console.log("Inside inner");
}
 
function outer() {
  console.log("Inside outer");
  inner();
  console.log("Back inside outer");
}
 
outer();
console.log("Back in global");

This example demonstrates three levels of context nesting. The global context runs outer, outer runs inner, and each return unwinds the stack in reverse order.

The output confirms this exact order of context creation and destruction.

texttext
Inside outer
Inside inner
Back inside outer
Back in global

The global context runs first. Calling outer pushes a function context. Calling inner pushes another.

When inner returns, its context is popped and destroyed. Then outer returns and its context is destroyed. The global context is now on top again.

For more on how the call stack manages this process, read /javascript/how-the-js-call-stack-handles-function-execution.

Execution Context and Closures

A closure is created when a function retains access to variables from an outer execution context, even after that outer context has been destroyed.

javascriptjavascript
function createCounter() {
  let count = 0;
 
  return function increment() {
    count += 1;
    return count;
  };
}
 
const counter = createCounter();
console.log(counter());
console.log(counter());

When createCounter returns, its execution context is removed from the call stack. But the returned increment function still holds a reference to count through the scope chain. That reference is the closure.

The variable count stays alive because something still needs it.

For a full explanation of closures, see /javascript/javascript-closures-deep-dive-complete-guide.

The Eval and Block Contexts

In strict mode, let and const declarations inside a block also get their own lexical environment, sometimes called a block-level context. The eval function also creates its own execution context with its own variable environment, which is one reason eval is both powerful and dangerous.

Rune AI

Rune AI

Key Insights

  • An execution context is the environment holding variables, scope chain, and the this keyword for running code.
  • The global context is created once. Function contexts are created for every call and destroyed on return.
  • The creation phase allocates memory for variables and functions before any code runs.
  • var variables initialize to undefined. let and const are created but stay uninitialized in the Temporal Dead Zone.
  • Each context has a reference to its outer environment, forming the scope chain.
RunePowered by Rune AI

Frequently Asked Questions

How many execution contexts can exist at once?

Only one execution context runs at a time on the main thread. Others wait on the call stack. The currently running context is the one on top of the stack.

What is the difference between global and function execution context?

The global execution context is created once when your script starts. A function execution context is created each time a function is called and is destroyed when the function returns.

Conclusion

Every line of JavaScript runs inside an execution context. The global context starts the program. Every function call creates a new context with its own variable environment, scope chain, and this binding. The creation phase sets up variables and functions before code runs, which is why hoisting works the way it does. Understanding contexts makes scope, closures, and the call stack much easier to reason about.| Phase | What Happens | |---|---| | Context creation | Engine sets up scope chain, variable environment, and the this keyword | | Creation phase | Allocates memory for variables and functions; var gets undefined, let and const are uninitialized | | Execution phase | Runs code line by line; assigns values; calls functions | | Function call | Current context pauses; new context is created and pushed onto the stack | | Function return | Current context is popped off the stack and destroyed; previous context resumes | The lifecycle is mechanical and predictable. Every function call creates a context. Every return destroys one.