Closures and Lexical Scope

Advanced
130 XP
55 min
Lesson Content

Closures and Lexical Scope

A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned. Closures are fundamental to JavaScript and enable powerful patterns.

What is a Closure?

function outer() {
  let outerVar = 'I am outside!';
  
  function inner() {
    console.log(outerVar); // Can access outerVar
  }
  
  return inner;
}

const innerFunc = outer();
innerFunc(); // 'I am outside!' - closure preserved outerVar

Common Closure Patterns

// Module pattern
const counter = (function() {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count
  };
})();

// Function factories
function createMultiplier(multiplier) {
  return function(number) {
    return number * multiplier;
  };
}

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

Closures in Loops

// Problem: All functions reference same variable
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // Prints 3, 3, 3
}

// Solution: Use let or IIFE
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // Prints 0, 1, 2
}

Practical Uses

  • Data privacy and encapsulation
  • Function factories
  • Event handlers
  • Memoization
  • Currying
Example Code

Create and use closures for data privacy and function factories

// Module pattern with closure
const bankAccount = (function() {
  let balance = 0;
  
  return {
    deposit: function(amount) {
      balance += amount;
      return balance;
    },
    withdraw: function(amount) {
      if (amount <= balance) {
        balance -= amount;
        return balance;
      }
      return 'Insufficient funds';
    },
    getBalance: function() {
      return balance;
    }
  };
})();

console.log('Initial:', bankAccount.getBalance());
console.log('After deposit:', bankAccount.deposit(100));
console.log('After withdraw:', bankAccount.withdraw(30));
console.log('Balance:', bankAccount.getBalance());

// Function factory
function createGreeter(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}

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

console.log(sayHello('Alice'));
console.log(sayHi('Bob'));

// Memoization with closure
function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

const slowFunction = (n) => {
  // Simulate slow operation
  return n * 2;
};

const fastFunction = memoize(slowFunction);
console.log('First call:', fastFunction(5));
console.log('Cached call:', fastFunction(5));

Expected Output:

Initial: 0
After deposit: 100
After withdraw: 70
Balance: 70
Hello, Alice!
Hi, Bob!
First call: 10
Cached call: 10
Study Tips
  • Read the theory content thoroughly before practicing
  • Review the example code to understand key concepts
  • Proceed to the Practice tab when you're ready to code