What is a Closure?

A closure is a function that remembers the variables from its outer scope even after that outer function has returned. In other words, a function "closes over" its surrounding environment.

Every function in JavaScript is a closure — but the interesting ones are those that access variables from an outer function after the outer function has finished executing.

MDN Definition: A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

Lexical Scope — The Foundation

To understand closures you first need to understand lexical scope. Scope in JavaScript is determined by where variables are written in the source code — not where they are called from.

function outer() {
  const message = 'Hello from outer!'; // outer scope

  function inner() {
    console.log(message); // inner can see message
  }

  inner(); // logs: Hello from outer!
}

outer();
Outer Function Scope
const message = 'Hello from outer!'
Inner Function Scope
console.log(message) ← looks up to outer scope ✓

The inner function can access message because it is lexically inside the outer function. This "lookup" follows the scope chain — inner → outer → global.

Closure in Action

The real magic of closures happens when the inner function escapes from the outer function — either by being returned or passed as a callback — and continues to remember the outer variables.

function makeCounter() {
  let count = 0; // this variable is "closed over"

  return function () {
    count++;
    console.log(count);
  };
}

const counter = makeCounter();

counter(); // 1
counter(); // 2
counter(); // 3

makeCounter has returned, but count is still alive because counter (the returned function) holds a reference to it. Each call increments the same count variable.

Key insight: Closures keep variables alive as long as a function that references them still exists. The variable is not garbage-collected.

Practical Real-World Examples

1. Private Variables (Encapsulation)

Closures let you simulate private state — variables that can't be accessed from the outside.

function createUser(name) {
  let loginCount = 0; // private — not accessible outside

  return {
    login() {
      loginCount++;
      console.log(`${name} logged in. Total logins: ${loginCount}`);
    },
    getCount() {
      return loginCount;
    }
  };
}

const user = createUser('Jaydeep');
user.login();     // Jaydeep logged in. Total logins: 1
user.login();     // Jaydeep logged in. Total logins: 2
console.log(user.loginCount); // undefined — truly private!

2. Partial Application (Pre-filled Arguments)

function multiply(a) {
  return function (b) {
    return a * b;
  };
}

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

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

3. Event Handlers with Preserved State

function attachClickLogger(buttonId) {
  let clickCount = 0;

  document.getElementById(buttonId).addEventListener('click', function () {
    clickCount++;
    console.log(`Button ${buttonId} clicked ${clickCount} times`);
  });
}

attachClickLogger('submitBtn');

Closures and Loops — Classic Gotcha

One of the most common interview questions involves closures inside loops.

// WRONG — using var
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // logs 3, 3, 3 — not 0, 1, 2!
  }, 1000);
}

Why? var is function-scoped (not block-scoped). All three callbacks share the same i variable. By the time they run, the loop has finished and i is 3.

There are two fixes:

// FIX 1 — use let (block-scoped, creates new binding each iteration)
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // 0, 1, 2 — correct!
  }, 1000);
}

// FIX 2 — use an IIFE to capture i by value
for (var i = 0; i < 3; i++) {
  (function (captured) {
    setTimeout(function () {
      console.log(captured); // 0, 1, 2 — correct!
    }, 1000);
  })(i);
}

Module Pattern with Closures

Before ES Modules, the module pattern used closures to create a public API while hiding internal state.

const cartModule = (function () {
  // private
  const items = [];

  // public API
  return {
    addItem(item) {
      items.push(item);
    },
    removeItem(name) {
      const idx = items.findIndex(i => i.name === name);
      if (idx !== -1) items.splice(idx, 1);
    },
    getTotal() {
      return items.reduce((sum, i) => sum + i.price, 0);
    },
    getCount() {
      return items.length;
    }
  };
})();

cartModule.addItem({ name: 'Laptop', price: 999 });
cartModule.addItem({ name: 'Mouse', price: 29 });
console.log(cartModule.getTotal()); // 1028
console.log(cartModule.getCount()); // 2

IIFE (Immediately Invoked Function Expression) runs once, returns the public object, and the private items array lives on as a closure.

Memoization Using Closures

Memoization is a performance optimization that caches the results of expensive function calls. Closures make it elegant.

function memoize(fn) {
  const cache = {}; // closed over by the returned function

  return function (...args) {
    const key = JSON.stringify(args);
    if (cache[key] !== undefined) {
      console.log('From cache!');
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

function slowSquare(n) {
  // imagine this is slow
  return n * n;
}

const fastSquare = memoize(slowSquare);

fastSquare(5);  // computed: 25
fastSquare(5);  // From cache! 25
fastSquare(10); // computed: 100

Common Mistakes

MistakeProblemFix
Using var in loops with async callbacks All callbacks share the same variable Use let or an IIFE
Expecting closures to capture values Closures capture references, not values Copy the value into a new variable
Creating closures in tight loops Memory leak — many closures holding large objects Nullify references when done
Mutating shared closed-over state Unexpected side effects across closures Use separate closures or immutable data

Remember: Closures hold references to variables, not copies. If you change the variable after the closure is created, the closure sees the new value.