Closures

Understanding closures and lexical scoping in JavaScript

What is a Closure?

A closure is a function that has access to variables from its outer (enclosing) function's scope, even after the outer function has returned. Closures are created every time a function is created in JavaScript.

The Three Scope Chains

A closure has access to:

  • 1. Its own scope — Variables defined between its curly braces
  • 2. Outer function's variables — Variables from the enclosing function
  • 3. Global variables — Variables in the global scope

Lexical Scoping

JavaScript uses lexical scoping, meaning a function's scope is determined by where it's defined in the source code, not where it's called.

function outer() {
  const message = "Hello from outer!";
  
  function inner() {
    // inner() has access to 'message' due to lexical scoping
    console.log(message);
  }
  
  return inner;
}

const closureFn = outer();
closureFn(); // "Hello from outer!" - still has access!

Basic Closure Example

Here's a simple closure that creates a counter:

function createCounter() {
  let count = 0; // Private variable
  
  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount());  // 1

// count is private - can't access directly
console.log(counter.count); // undefined

Practical Use Cases

1. Data Privacy / Encapsulation

function createBankAccount(initialBalance) {
  let balance = initialBalance; // Private
  
  return {
    deposit(amount) {
      if (amount > 0) {
        balance += amount;
        return balance;
      }
    },
    withdraw(amount) {
      if (amount > 0 && amount <= balance) {
        balance -= amount;
        return balance;
      }
      return "Insufficient funds";
    },
    getBalance() {
      return balance;
    }
  };
}

const account = createBankAccount(100);
account.deposit(50);   // 150
account.withdraw(30);  // 120
// balance is protected - no direct access

2. Function Factories

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

const double = multiplier(2);
const triple = multiplier(3);

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

3. Memoization (Caching)

function memoize(fn) {
  const cache = {}; // Closure over cache
  
  return function(...args) {
    const key = JSON.stringify(args);
    
    if (cache[key]) {
      console.log("From cache");
      return cache[key];
    }
    
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

const expensiveOperation = memoize((n) => {
  console.log("Computing...");
  return n * n;
});

expensiveOperation(5); // "Computing..." → 25
expensiveOperation(5); // "From cache" → 25

Common Pitfall: Closures in Loops

A classic closure gotcha with var:

// ❌ Problem: All callbacks share the same 'i'
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // Prints 3, 3, 3
  }, 1000);
}

// ✅ Solution 1: Use let (block scoping)
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // Prints 0, 1, 2
  }, 1000);
}

// ✅ Solution 2: Create a closure with IIFE
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // Prints 0, 1, 2
    }, 1000);
  })(i);
}

Event Handlers and Closures

function setupButton(buttonId, message) {
  const button = document.getElementById(buttonId);
  
  button.addEventListener("click", function() {
    // This handler closes over 'message'
    alert(message);
  });
}

setupButton("btn1", "Hello!");
setupButton("btn2", "Goodbye!");

💡 Key Takeaways

  • • Closures give functions memory - they remember their lexical environment
  • • Use closures for data privacy, function factories, and memoization
  • • Use let instead of var in loops to avoid closure issues
  • • Closures keep references to variables, not copies of values
  • • Be mindful of memory - closures keep outer variables in memory