Scope determines where variables are accessible. Closures allow functions to remember variables from their outer scope even after that scope has finished executing.

Types of Scope

Global Scope

Variables declared outside any function or block:

  let globalVar = 'I am global';

function showGlobal() {
    console.log(globalVar); // accessible
}
  

Function Scope

Variables declared with var inside a function:

  function example() {
    var fnScoped = 'inside function';
    console.log(fnScoped);
}
// console.log(fnScoped); // ReferenceError
  

Block Scope

Variables declared with let or const inside {}:

  if (true) {
    let blockVar = 'block';
    const alsoBlock = 'block';
}
// console.log(blockVar); // ReferenceError
  

Lexical Scope

JavaScript uses lexical (static) scope — a function’s scope is determined by where it is written in the source code, not where it is called.

  let outer = 'outer';

function outerFn() {
    let inner = 'inner';
    function innerFn() {
        console.log(outer, inner); // both accessible
    }
    innerFn();
}
  

Closures

A closure is created when a function retains access to variables from its outer lexical environment.

  function createCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
  

Each call to createCounter() creates a new independent closure with its own count.

Practical Closure Uses

Private variables (module pattern)

  function createBankAccount(initialBalance) {
    let balance = initialBalance;

    return {
        deposit(amount) {
            balance += amount;
            return balance;
        },
        withdraw(amount) {
            if (amount <= balance) {
                balance -= amount;
            }
            return balance;
        },
        getBalance() {
            return balance;
        }
    };
}

const account = createBankAccount(100);
account.deposit(50);   // 150
account.withdraw(30);  // 120
// account.balance is not accessible — private
  

Factory functions

  function multiply(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
  

Closures in Loops (Common Pitfall)

  // Problem: var is function-scoped, all callbacks share same i
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 3, 3, 3
}

// Fix 1: use let (block scope)
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 0, 1, 2
}

// Fix 2: IIFE
for (var i = 0; i < 3; i++) {
    ((j) => setTimeout(() => console.log(j), 100))(i);
}
  

Scope Chain

When a variable is referenced, JavaScript looks up the scope chain: current scope → outer scope → … → global scope.

  let a = 1;

function outer() {
    let b = 2;
    function inner() {
        let c = 3;
        console.log(a, b, c); // 1, 2, 3
    }
    inner();
}
  

Understanding scope and closures is essential for advanced JavaScript patterns, callbacks, and asynchronous code.