Learning Objectives

  • Understand what lexical scope means
  • Learn how the scope chain works
  • See how JavaScript resolves variable references
  • Understand the relationship between lexical scope and closures

What Is Lexical Scope?

Lexical scope (also called static scope) means that the scope of a variable is determined by its position in the source code. In other words, where you write your code determines what variables are accessible.

This is different from dynamic scope (used in some other languages), where scope is determined by the call stack at runtime. JavaScript uses lexical scope exclusively.

const globalVar = "global";

function outer() {
    const outerVar = "outer";
    
    function inner() {
        const innerVar = "inner";
        console.log(innerVar);   // ✓ Can access
        console.log(outerVar);   // ✓ Can access
        console.log(globalVar);  // ✓ Can access
    }
    
    inner();
}

outer();

The inner function can access variables from its own scope, its parent's scope (outer), and the global scope. This is determined by where inner is written in the code, not where it's called.

The Scope Chain

When JavaScript looks for a variable, it follows the scope chain:

  1. First, it looks in the current (local) scope
  2. If not found, it looks in the parent scope
  3. It continues up the chain until it reaches the global scope
  4. If still not found, it throws a ReferenceError
┌─────────────────────────────────────────┐ │ Global Scope │ │ globalVar = "global" │ │ │ │ ┌───────────────────────────────────┐ │ │ │ outer() Scope │ │ │ │ outerVar = "outer" │ │ │ │ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ inner() Scope │ │ │ │ │ │ innerVar = "inner" │ │ │ │ │ │ │ │ │ │ │ │ Scope Chain: │ │ │ │ │ │ 1. inner scope ──┐ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────┼──────────┘ │ │ │ │ 2. outer scope ◄───┘ │ │ │ │ │ │ │ └───────────────────┼───────────────┘ │ │ 3. global scope ◄───┘ │ │ │ └─────────────────────────────────────────┘

Lexical Scope in Action

Let's see how lexical scope determines variable accessibility:

const x = "global x";

function first() {
    const x = "first x";
    
    function second() {
        const x = "second x";
        console.log(x); // Which x?
    }
    
    second();
}

first(); // Output: "second x"

The output is "second x" because JavaScript finds x in the local scope first. It doesn't need to look further up the scope chain.

Variable Shadowing

When a variable in an inner scope has the same name as a variable in an outer scope, the inner variable shadows (hides) the outer one:

const message = "global";

function showMessage() {
    const message = "local"; // Shadows global message
    console.log(message);    // Output: "local"
}

showMessage();
console.log(message); // Output: "global"

Lexical Scope and Closures

Here's where it gets interesting. Because of lexical scope, a function "remembers" the scope in which it was created:

function createGreeter(greeting) {
    // greeting is in this scope
    
    return function(name) {
        // This inner function has access to greeting
        // because of lexical scope
        console.log(`${greeting}, ${name}!`);
    };
}

const sayHello = createGreeter("Hello");
const sayHi = createGreeter("Hi");

sayHello("Alice"); // Output: "Hello, Alice!"
sayHi("Bob");      // Output: "Hi, Bob!"

Each returned function "closes over" its own greeting variable. This is a closure in action!

The Scope Chain Never Changes

An important point: the scope chain is determined when the function is defined, not when it's called:

const value = "global";

function outer() {
    const value = "outer";
    
    function inner() {
        console.log(value); // Will always log "outer"
    }
    
    return inner;
}

const myFunc = outer();

// Even if we create a new value variable here
const value = "new global";

myFunc(); // Still outputs: "outer"

The inner function was defined inside outer, so it will always reference outer's value, regardless of where it's called.

Multiple Nested Scopes

The scope chain can be as deep as you need:

const level1 = "L1";

function a() {
    const level2 = "L2";
    
    function b() {
        const level3 = "L3";
        
        function c() {
            const level4 = "L4";
            
            // Can access all levels
            console.log(level1); // "L1"
            console.log(level2); // "L2"
            console.log(level3); // "L3"
            console.log(level4); // "L4"
        }
        
        c();
    }
    
    b();
}

a();

Practical Example: Configuration

Lexical scope is perfect for creating configured functions:

function createMultiplier(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

console.log(double(5));     // 10
console.log(triple(5));     // 15
console.log(quadruple(5));  // 20

Each function "remembers" its own multiplier value through lexical scope and closures.

Key Takeaways

  • Lexical scope means scope is determined by code structure, not runtime
  • ✅ The scope chain is the path JavaScript follows to resolve variables
  • ✅ JavaScript looks from inner scope → outer scope → global scope
  • Variable shadowing occurs when inner variables hide outer ones
  • ✅ Functions remember their lexical scope, enabling closures
  • ✅ The scope chain is fixed at function definition, not invocation

Next Steps

Now that you understand lexical scope and the scope chain, you're ready to create your first practical closures. In the next lesson, we'll build several closure examples and explore common patterns.