Lexical Scoping & Scope Chain

Med

Lexical scope means that scope is determined by where variables and functions are declared in the source code (at write time), not where they are called (at runtime). This creates a static scope chain that never changes. Understanding lexical scope is the foundation for understanding closures - a function always has access to the scope where it was defined, no matter where it is executed.

Interactive Visualization

Scope Nesting

Variables declared outside any function/block

globalVar

Key Points

  • Lexical = "where written" not "where called"
  • Scope chain: inner scope → outer scope → global
  • Variable lookup follows the scope chain until found
  • Scope is static - doesn't change based on how function is called
  • This is different from "this" binding which is dynamic
  • Lexical scope enables closures

Code Examples

Lexical vs Dynamic Scope

const x = "global";

function outer() {
  const x = "outer";
  return inner;
}

function inner() {
  console.log(x); // "global" - looks at where DEFINED, not called!
}

const fn = outer();
fn(); // "global" - inner's scope is determined at definition

// With dynamic scope, this would print "outer"
// JavaScript uses lexical scope, not dynamic scope

inner() uses the scope where it was defined, not where outer() was called

Scope Chain Lookup

const a = "global-a";

function level1() {
  const b = "level1-b";
  
  function level2() {
    const c = "level2-c";
    
    console.log(a); // "global-a" - found in global
    console.log(b); // "level1-b" - found in level1
    console.log(c); // "level2-c" - found in current scope
  }
  
  level2();
}

level1();

// Lookup path for 'a': level2 → level1 → global ✅ Found!

JavaScript walks up the scope chain looking for variables

Lexical Scope Never Changes

function createFunction() {
  const message = "Hello from createFunction";
  
  return function() {
    console.log(message);
  };
}

const fn = createFunction();

// Even though createFunction has finished executing...
// fn still has access to its scope!
fn(); // "Hello from createFunction"

// The scope chain is fixed at definition time
// and preserved (closure)

The returned function carries its lexical scope with it - this is closure

Shadowing with Lexical Scope

const value = "global";

function outer() {
  const value = "outer";
  
  function inner() {
    // const value = "inner";
    console.log(value); // "outer" - finds nearest in scope chain
  }
  
  inner();
}

outer();

Inner scope shadows outer scope - JavaScript finds the nearest definition in the lexical scope chain

Scope Chain vs This Binding

const obj = {
  name: "Object",
  method: function() {
    console.log(this.name);    // "Object" - dynamic binding
    
    const arrow = () => {
      console.log(this.name);  // "Object" - arrow uses lexical this
    };
    arrow();
  }
};

const fn = obj.method;
fn(); // this.name = undefined (or global name)
      // But arrow still uses obj's scope

// Scope (lexical) ≠ this (dynamic)

Scope is lexical (static), but this is dynamic. Arrow functions use lexical this.

Common Mistakes

  • Confusing lexical scope with dynamic scope
  • Expecting scope to change based on where function is called
  • Confusing scope chain with prototype chain
  • Confusing scope with this binding

Interview Tips

  • Emphasize: scope is determined at write time, this at runtime
  • Trace scope chain explicitly in examples
  • Contrast lexical scope with dynamic scope
  • Connect lexical scope to closures