The Runtime Environment

The Call Stack

A. Definition

The Call Stack is the Data Structure (LIFO) used to manage execution of functions. It lives inside the Stack Memory (the physical RAM). While the Stack Memory provides the physical storage space, the Call Stack tracks the logical order of execution of functions.

It answers two questions for the engine:

  1. What function is currently being executed? (The Active Stack Frame). Whatever stack frame the Stack Pointer is pointing to right now is the active stack frame.

  2. What to do after code execution finishes ? (The Return Address). Once Function is popped off Stack Pointer moves down thereby updating the active stack frame.

B. Composition: Execution Contexts

The items stacked in the Call Stack are called Execution Contexts.

  • Global Execution Context (GEC): The default context. It is created when the file first loads. It sits at the very bottom of the stack and is only popped off when the program terminates (e.g., closing the browser window).
  • Function Execution Context (FEC): Created whenever a function is invoked. Each function call gets its own unique context containing its arguments and local variables.

C. Mechanics: Push and Pop

It operates on a strictly LIFO (Last In, First Out) basis.

  • Push: When a function is invoked, the engine creates a new Function Execution Context and places it on top of the pile. The Stack Pointer moves up.
  • Pop: When a function returns (finishes), the engine removes the top context. The Stack Pointer moves down, and control returns to the context below it.

D. Limits: Stack Overflow

Since the Stack Memory has a fixed size (determined by the OS), there is a limit to how many contexts can be pushed. If we create an infinite recursion (Function A calls Function A forever), the stack grows until it hits the physical ceiling, causing a Stack Overflow (e.g., RangeError: Maximum call stack size exceeded).


The Execution Context & Scoping Rules

A. Logical vs. Physical

  • Execution Context: The abstract specification defined by ECMAScript. The official rulebook (ECMAScript) says that every time a function runs, the computer must track its variables (Execution Context). It doesn't tell the computer exactly how to manage the memory chips to do that—that part is up to the engine developers.
  • Stack Frame (The Physical Container): The actual slice of physical memory allocated on the Call Stack. The Execution Context lives inside this frame.

B. Internal Anatomy

Every Stack Frame contains three specific components required to run the code:

  1. The Variable Environment: A lookup table that binds Identifiers (both Variable names and Function names) to specific Memory Slots.
  2. The Memory Slots: The physical cells that hold the actual content.
    • For Primitives: The slot holds the direct Value.
    • For Reference Types (Objects & Functions): The slot holds the Memory Address linking to the Heap.
  3. The outer Pointer: A specific memory reference linking this frame to its Parent's Variable Environment. This is the physical mechanism behind the Scope Chain.

C. The Three Scope Levels

The Scope defines the boundary in which a variable is accessible.

  1. Global Scope: Variables here are accessible everywhere. Created once when the script starts.
  2. Function Scope: Created when a function runs. Variables defined with var are trapped here.
  3. Block Scope (ES6): Created by curly braces (if statements, loops). Variables defined with let and const are trapped here.
    • Note: var ignores block scope and leaks out to the nearest function or global scope.

Lifecycle of Execution Context

This is the most critical concept for understanding Hoisting. An Execution Context is not created instantly; it is built in two distinct passes.

Phase 1: The Creation Phase (Hoisting & Analysis)

Before the code executes line-by-line, the engine scans the code to set up the environment.

  1. Variable Hoisting:

    • var: The engine registers the identifier in the Variable Environment, reserves a physical slot in Stack Memory, and initializes it to undefined.
    • let / const: The engine registers the identifier and reserves a physical slot in Stack Memory, but marks it as Uninitialized. Accessing it before initialization results in a Temporal Dead Zone (TDZ) error.
  2. Function Hoisting (The Two Types):

    • Function Declaration (function name() ): The engine creates the Function Object in the Heap immediately and assigns its address to the Stack Slot. It is fully usable before its definition line.
    • Function Expression (var name = function() ): The engine treats this as a variable. It hoists the identifier name but assigns it undefined. The function object is not created yet. Calling it before definition causes a TypeError.
  3. Static Scope Analysis:

    • This is where the Scope Chain is physically constructed using pointers.

    • The Dual-Pointer Setup : When an Execution Context is created, the engine determines the value of the outer pointer for both environment records.

      • Variable Environment :
        • Capacity: A Function Execution Context has only one Variable Environment.
        • Outer Pointer: Links directly to the outer scope. It ignores blocks () and is lookup table for var and function declarations for the entire function.
      • Lexical Environment :
        • Capacity: A Function Execution Context can have multiple Lexical Environments.
        • Dynamic Nature: While the Execution Context has a base Lexical Environment, every time the engine enters a block (like if, for, or ), it creates a new, nested Lexical Environment for that block to store let and const.
        • Outer Pointer: Each new block-level Lexical Environment has an outer pointer that points to the previous LE (forming a mini-chain within the function).
    • Environment Pointer ([[Environment]]):

      • Location: Function Object (Heap Memory).
      • Role: It records the Lexical Environment where the function was physically defined.
    • Outer Pointer (outer):

      • Location: Stack Frame (Execution Context).
      • Role: When a function runs, the engine copies the [[Environment]] value into the Execution Context outer pointer.
      • Both the Variable Environment and the Base Lexical Environment share the same value in their outer pointers, ensuring they both look up to the same parent.
    • Closure Scan (Heap Promotion): The engine scans for inner functions. If an inner function uses a variable from the current scope, the engine realizes variable cannot live on the temporary Stack.

      • Action: The engine moves variable from the Stack to a persistent Context Object in the Heap.
      • Result: The inner function's [[Environment]] pointer now links to this Heap Object, ensuring the variable survives even after the Stack Frame pops.

Phase 2: The Execution Phase

The engine runs through the code again, this time line-by-line.

  1. Assignment:

    • The engine performs the lookup using the Variable Environment and Lexical Environment .
    • It writes the actual values (or new Object addresses) into the Memory Slots, overwriting the undefined placeholders.
  2. Invocation:

    • If the current line calls a function, the engine pauses the current context and pushes a new Execution Context onto the stack, restarting the cycle (Phase 1) for that new function.

Summary Table: Creation vs. Execution

FeatureCreation PhaseExecution Phase
ActionScanning & AllocationRunning & Assignment
var stateundefinedAssigned Value
let stateUninitialized (TDZ)Assigned Value
Func DeclarationAddress in Slot (Usable)Skipped (Already done)
Func ExpressionundefinedObject Created & Assigned
Scope Chainouter pointer set (Static)outer pointer used (Lookup)