What Is a Closure?

A closure is a function that retains access to its outer (enclosing) scope even after that outer function has returned. In plain English: a function "closes over" the variables in the environment where it was created — hence the name.

This is not a quirk or a bug. It's a fundamental part of how JavaScript's lexical scoping works, and understanding it unlocks a huge range of patterns used throughout real codebases.

A Simple Example

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

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

Here, makeCounter returns an inner function. Even though makeCounter has finished executing, the inner function still has access to count. That's a closure in action — count is kept alive in memory because the inner function references it.

Why Closures Exist: Lexical Scope

JavaScript uses lexical scoping, which means the scope of a variable is determined by where it's written in the source code — not where a function is called. When a function is defined, it captures a reference to the scope chain at that point. This captured scope is the closure.

Practical Use Cases

1. Data Privacy / Encapsulation

Closures let you simulate private variables without classes:

function createBankAccount(initialBalance) {
  let balance = initialBalance;
  return {
    deposit: (amount) => { balance += amount; },
    withdraw: (amount) => { balance -= amount; },
    getBalance: () => balance,
  };
}

const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150

The balance variable is completely inaccessible from outside — only the returned methods can touch it.

2. Function Factories

You can use closures to generate specialized functions on the fly:

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

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

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

3. Event Handlers and Callbacks

Closures are everywhere in async code and DOM event handling, where inner functions need to "remember" values from the time they were set up — such as loop indices or configuration options.

A Common Closure Pitfall

The classic var-in-a-loop bug catches many developers off guard:

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

Because var is function-scoped, all three callbacks share the same i. By the time the timeouts fire, the loop has finished and i is 3. The fix: use let, which creates a new binding per iteration.

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

Key Takeaways

  • A closure is a function paired with its surrounding lexical environment.
  • Closures keep referenced variables alive even after the outer function returns.
  • They enable data encapsulation, function factories, and stateful callbacks.
  • Understanding closures is essential for debugging async code and writing clean, modular JavaScript.

Closures aren't magic — they're a logical consequence of how JavaScript handles scope. Once you internalize that, you'll start seeing them everywhere and writing better code for it.