Scope Chain in JavaScript

The scope chain is how JavaScript resolves variable names. It's the set of rules that determines how JavaScript looks up variables and where they can be accessed.

Lexical Scope

// Variables are accessible from inner functions
const globalVar = 'I am global';

function outerFunction() {
  const outerVar = 'I am from outer';
  
  function innerFunction() {
    const innerVar = 'I am from inner';
    console.log(innerVar);    // Accessible
    console.log(outerVar);    // Accessible
    console.log(globalVar);   // Accessible
  }
  
  innerFunction();
  console.log(innerVar);      // ReferenceError
}

outerFunction();
console.log(outerVar);        // ReferenceError

Block Scope

// let and const create block scope
{
  let blockVar = 'block scoped';
  const constVar = 'also block scoped';
  var functionVar = 'function scoped';
}

console.log(functionVar);  // Accessible
console.log(blockVar);     // ReferenceError
console.log(constVar);     // ReferenceError

// Loop scope
for (let i = 0; i < 3; i++) {
  const square = i * i;
  console.log(square);
}
console.log(i);        // ReferenceError
console.log(square);   // ReferenceError

Function Scope

// var creates function scope
function functionScope() {
  var functionVar = 'function scoped';
  let blockVar = 'block scoped';
  
  if (true) {
    var anotherFunctionVar = 'still function scoped';
    let anotherBlockVar = 'block scoped';
  }
  
  console.log(functionVar);         // Accessible
  console.log(anotherFunctionVar);  // Accessible
  console.log(anotherBlockVar);     // ReferenceError
}

// Hoisting with var
function hoistingExample() {
  console.log(hoistedVar);  // undefined
  var hoistedVar = 'hoisted';
}

Closure and Scope

// Closures retain their scope chain
function createCounter() {
  let count = 0;  // Enclosed variable
  
  return {
    increment() {
      return ++count;  // Access to parent scope
    },
    decrement() {
      return --count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.count);        // undefined

Module Scope

// Each module has its own scope
// moduleA.js
const privateVar = 'private';
export const publicVar = 'public';

// moduleB.js
import { publicVar } from './moduleA.js';
console.log(publicVar);    // Accessible
console.log(privateVar);   // ReferenceError

// Global scope pollution
window.globalVar = 'polluting global scope';
console.log(globalVar);    // Accessible everywhere

Common Interview Follow-up Questions

  1. How does the scope chain work with nested functions?
  2. What's the difference between lexical and dynamic scope?
  3. How does block scope differ from function scope?
  4. How do closures maintain their scope chain?

Best Practices

  • Prefer const and let over var
  • Keep functions pure and avoid global scope
  • Use modules to encapsulate code
  • Be mindful of closure memory implications
  • Understand hoisting behavior
  • Use block scope for better variable isolation