Closures

What is a Closure?

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 created every time a function is created.

Try Closures

Code Editor
Console Output
Click "Run" to execute your code...

Example 1: Basic Closure

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

const myFunction = outer();
myFunction(); // I am from outer

// Even though outer() has finished executing,
// inner() still has access to outerVar!

// Another example
function createGreeting(greeting) {
  return function(name) {
    console.log(`${greeting}, ${name}!`);
  };
}

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

sayHello('John');  // Hello, John!
sayHi('Jane');     // Hi, Jane!

Example 2: Counter with Closure

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

const counter1 = createCounter();
console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter1.increment()); // 3
console.log(counter1.getCount());  // 3
console.log(counter1.decrement()); // 2
console.log(counter1.reset());     // 0

// Each counter has its own private count
const counter2 = createCounter();
console.log(counter2.increment()); // 1
console.log(counter1.getCount());  // 0 (not affected!)

Example 3: Closure in Loops

// Problem: var doesn't create closure per iteration
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 3, 3, 3
  }, 1000);
}

// Solution 1: Use let (creates new binding per iteration)
for (let j = 0; j < 3; j++) {
  setTimeout(function() {
    console.log(j); // 0, 1, 2
  }, 1000);
}

// Solution 2: IIFE creates closure
for (var k = 0; k < 3; k++) {
  (function(x) {
    setTimeout(function() {
      console.log(x); // 0, 1, 2
    }, 1000);
  })(k);
}

// Solution 3: Function factory
function createLogger(index) {
  return function() {
    console.log(index);
  };
}

for (var m = 0; m < 3; m++) {
  setTimeout(createLogger(m), 1000); // 0, 1, 2
}

Example 4: Private Variables and Methods

function BankAccount(initialBalance) {
  // Private variables
  let balance = initialBalance;
  let transactions = [];
  
  // Private method
  function recordTransaction(type, amount) {
    transactions.push({
      type,
      amount,
      date: new Date(),
      balance: balance
    });
  }
  
  // Public methods (closure over private variables)
  return {
    deposit: function(amount) {
      if (amount > 0) {
        balance += amount;
        recordTransaction('deposit', amount);
        return `Deposited: $${amount}. New balance: $${balance}`;
      }
      return 'Invalid amount';
    },
    
    withdraw: function(amount) {
      if (amount > 0 && amount <= balance) {
        balance -= amount;
        recordTransaction('withdrawal', amount);
        return `Withdrew: $${amount}. New balance: $${balance}`;
      }
      return 'Invalid amount or insufficient funds';
    },
    
    getBalance: function() {
      return balance;
    },
    
    getTransactions: function() {
      return [...transactions]; // Return copy, not reference
    }
  };
}

const myAccount = new BankAccount(1000);
console.log(myAccount.deposit(500));     // Deposited: $500. New balance: $1500
console.log(myAccount.withdraw(200));    // Withdrew: $200. New balance: $1300
console.log(myAccount.getBalance());     // 1300
// console.log(myAccount.balance);       // undefined (private!)
console.log(myAccount.getTransactions().length); // 2

Example 5: Function Factories

// Multiplier factory
function createMultiplier(multiplier) {
  return function(number) {
    return number * multiplier;
  };
}

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

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

// String formatter factory
function createFormatter(prefix, suffix) {
  return function(str) {
    return `${prefix}${str}${suffix}`;
  };
}

const addQuotes = createFormatter('"', '"');
const addParens = createFormatter('(', ')');
const addBrackets = createFormatter('[', ']');

console.log(addQuotes('Hello'));    // "Hello"
console.log(addParens('Hello'));    // (Hello)
console.log(addBrackets('Hello'));  // [Hello]

// API caller factory
function createAPI(baseURL) {
  return {
    get: function(endpoint) {
      return `GET ${baseURL}${endpoint}`;
    },
    post: function(endpoint, data) {
      return `POST ${baseURL}${endpoint} with ${JSON.stringify(data)}`;
    }
  };
}

const userAPI = createAPI('https://api.example.com/users');
const productAPI = createAPI('https://api.example.com/products');

console.log(userAPI.get('/123'));              // GET https://api.example.com/users/123
console.log(productAPI.post('/new', {name: 'Widget'})); // POST with data

Key Points

  • Closures allow functions to access variables from outer scope
  • Inner function maintains reference to outer scope, even after outer function returns
  • Closures are useful for data privacy and encapsulation
  • Every function in JavaScript is a closure
  • Closures can cause memory leaks if not managed properly
  • Common uses: callbacks, event handlers, private variables, function factories