Skip to main content
๐Ÿ”งadvanced

Execution Context

Every time JavaScript runs code, it creates an execution context. Understanding execution contexts, the creation phase, and the scope chain explains hoisting, closures, and this binding.

What is an Execution Context?

An execution context is the environment in which JavaScript code is evaluated and executed. It contains:

  1. Variable Environment โ€” bindings for variables and functions
  2. Lexical Environment โ€” the same + outer reference (scope chain)
  3. this binding โ€” what this refers to in this context

There are three types:

  • Global Execution Context (GEC) โ€” created when the script starts
  • Function Execution Context (FEC) โ€” created for each function call
  • Eval Execution Context โ€” created by eval() (avoid this)

The Call Stack

Execution contexts are managed in a stack (LIFO):

hljs javascript
function greet(name) {        // FEC created
  const message = "Hello, " + name;
  return message;
}

function main() {              // FEC created
  const result = greet("Alice"); // push FEC(greet)
  console.log(result);           // pop FEC(greet)
}

main(); // push FEC(main)
// GEC is always at the bottom

Call stack states:

[GEC] โ†’ script starts [GEC, FEC(main)] โ†’ main() called [GEC, FEC(main), FEC(greet)] โ†’ greet() called [GEC, FEC(main)] โ†’ greet() returns [GEC] โ†’ main() returns

Execution Context Phases

Every execution context goes through two phases:

Phase 1: Creation Phase

Before any code runs, JavaScript:

  1. Creates the this binding
  2. Creates the Lexical Environment (LexEnv) and Variable Environment (VarEnv)
  3. Allocates memory for variables and functions โ€” this is hoisting

What happens during creation:

  • Function declarations โ†’ stored with their full function body
  • var variables โ†’ stored as undefined
  • let/const โ†’ stored but uninitialized (Temporal Dead Zone)
hljs javascript
// What the engine "prepares" before running this code:
console.log(myVar);    // undefined โ€” var was hoisted and set to undefined
console.log(myFunc()); // "hello" โ€” function declaration fully hoisted

var myVar = "world";

function myFunc() {
  return "hello";
}

Phase 2: Execution Phase

Code runs line by line. Variables get assigned their actual values.

The Lexical Environment

A lexical environment is a data structure that maps identifier names to their values. Each context has:

  • Environment Record โ€” the actual bindings
  • Outer Reference โ€” pointer to the parent lexical environment

This outer reference is what creates the scope chain:

hljs javascript
const global = "I'm global";

function outer() {
  const outerVar = "I'm outer";

  function inner() {
    const innerVar = "I'm inner";
    // inner's outer reference โ†’ outer's LexEnv
    // outer's outer reference โ†’ global LexEnv
    console.log(innerVar, outerVar, global); // all accessible
  }

  inner();
}

Variable Environment vs Lexical Environment

In early spec, these were the same. Today the distinction is:

  • Variable Environment โ€” handles var bindings (function-scoped)
  • Lexical Environment โ€” handles let, const, function declarations (block-scoped)
hljs javascript
function example() {
  var a = 1;  // in VariableEnvironment
  let b = 2;  // in LexicalEnvironment

  if (true) {
    var c = 3;  // also in VariableEnvironment (function-scoped)
    let d = 4;  // new LexicalEnvironment for the block
    console.log(a, b, c, d); // all accessible
  }

  console.log(a, b, c); // accessible
  // console.log(d); // ReferenceError โ€” d's LexEnv is the block above
}

Closures Through Execution Contexts

When a function is created, it stores a reference to its current lexical environment. This is the closure:

hljs javascript
function makeAdder(x) {
  // FEC(makeAdder) created
  // LexEnv: { x: 5 }  (after execution)

  return function(y) {
    // This inner function stores a reference to makeAdder's LexEnv
    return x + y; // x is found in the outer LexEnv
  };
}

const add5 = makeAdder(5);
// FEC(makeAdder) is gone from the call stack
// BUT: the inner function still holds a reference to LexEnv { x: 5 }
// The LexEnv stays in memory because add5 references it

console.log(add5(3)); // 8 โ€” x found via the closure reference

this Binding in Execution Contexts

The this binding is determined at context creation:

hljs javascript
// Global context
// In browser: this = window
// In Node.js module: this = module.exports (or {} in CJS)
// In ES module: this = undefined

function regularFn() {
  // this is determined by HOW this function is called
  console.log(this);
}

const obj = {
  method: regularFn // when called as obj.method(), this = obj
};

const arrow = () => {
  // Arrow: no own this โ€” captured from creation context
  console.log(this);
};

Scope Chain Resolution

When a variable is referenced, JavaScript walks the scope chain:

hljs javascript
const x = "global x";

function level1() {
  const x = "level1 x"; // shadows global x

  function level2() {
    // no x here
    function level3() {
      console.log(x); // found in level1's LexEnv: "level1 x"
    }
    level3();
  }
  level2();
}

level1();

Resolution order: current context โ†’ parent โ†’ parent's parent โ†’ ... โ†’ global โ†’ ReferenceError.

Execution Context in Practice

Understanding execution contexts explains several behaviors:

hljs javascript
// 1. Hoisting
function hoistingExample() {
  console.log(a); // undefined (var hoisted)
  var a = 5;
  console.log(a); // 5
}

// 2. TDZ
function tdz() {
  // console.log(b); // ReferenceError (let in TDZ)
  let b = 5;
  console.log(b);   // 5
}

// 3. Closure preserving environment
function outer() {
  let count = 0;
  return () => count++;
}
const increment = outer();
increment(); increment(); increment();
// count is preserved in outer's LexEnv

// 4. this binding
const obj = {
  value: 42,
  getValue() {
    // FEC created with this = obj (method call)
    return this.value;
  }
};
โ–ถTry it yourself

Key Takeaways

  • Every execution context has a Variable Environment, Lexical Environment, and this binding
  • Creation phase: variables and functions are "prepared" (hoisting happens here)
  • Execution phase: code runs line by line, variables get their values
  • The scope chain is built from outer references in lexical environments
  • Closures work by storing a reference to the lexical environment at creation time
  • this binding is set during execution context creation

Ready to test your knowledge?

Take a quiz on what you just learned.

Take the Quiz โ†’