Lexical Scoping and Variable Access in JavaScript

Lexical scoping is a fundamental concept in JavaScript that dictates how variable access is resolved in nested functions based entirely on where functions and blocks are physically authored in the source code. In this model, an inner function permanently retains access to variables declared in its outer surrounding scopes, while outer scopes cannot reach inward. This article explores how lexical scope works, how the JavaScript engine resolves variable references through the scope chain, and why this mechanism is essential for features like closures.

Understanding Lexical Scope

The word “lexical” refers to the lexing stage of compilation, where the engine parses and categorizes code characters into tokens. Lexical scoping (also known as static scoping) means that variable scope is established at the time the code is written, not when it is executed.

In JavaScript, every time you define a function or a block (using let or const), a new lexical environment is defined. The engine determines variable accessibility strictly by reading the hierarchical structure of these definitions in your file.

How Lexical Scoping Determines Variable Access

Variable access under lexical scoping follows a strict, one-directional lookup process known as the scope chain. When the JavaScript engine encounters a variable reference, it resolves the value through the following steps:

  1. Local Scope Check: The engine first checks if the variable is declared within the currently executing function or block.
  2. Outer Scope Traversal: If the variable is not found locally, the engine steps outward to the parent lexical scope that physically wraps the current function.
  3. Ascending the Scope Chain: This outward traversal continues level by level through all nested parent functions.
  4. Global Scope: The final stop is the global scope. If the variable is located here, its value is used.
  5. Resolution Failure: If the variable cannot be found in the global scope, JavaScript throws a ReferenceError (in strict mode).

Key Rules of Variable Access

Code Example: The Scope Chain in Action

const globalVar = "I am global";

function outerFunction() {
    const outerVar = "I am in outerFunction";

    function innerFunction() {
        const innerVar = "I am in innerFunction";

        // Accessing local, outer, and global variables
        console.log(innerVar);   // Found locally
        console.log(outerVar);   // Found in outer lexical environment
        console.log(globalVar);  // Found in global lexical environment
    }

    innerFunction();

    // Attempting to access an inner variable from an outer scope
    // console.log(innerVar); // Uncaught ReferenceError: innerVar is not defined
}

outerFunction();

In this example, innerFunction has access to innerVar, outerVar, and globalVar because they exist at or above its position in the hierarchy. However, outerFunction cannot access innerVar.

The Relationship Between Lexical Scope and Closures

Lexical scoping is the foundational mechanism that allows closures to work in JavaScript. A closure is created when an inner function is returned or executed outside its original lexical environment, yet it still remembers and retains access to its parent variables. Because scoping is determined lexically at write-time, the inner function’s connection to its outer variables remains intact regardless of where or when that function is ultimately invoked.