What Is a Closure in JavaScript?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function makeCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

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

makeCounter runs once, returns a function, and is done. The call is over and its stack frame is gone. Yet every call to counter() still reads and updates count, a variable declared inside a function that has already finished. count isn’t reset to 0, and it isn’t copied. It’s the same variable, still live, three calls later.

That’s a closure: a function paired with a reference to the variables that were in scope where it was defined. Not a snapshot of their values. A live reference: if something else changes count, the counter sees the new value.

Why Does the Variable Survive After the Function Returns?

JavaScript resolves variable names lexically, by where they’re written in the source, not by who calls the function. For count += 1 inside the inner function, the engine looks outward through the enclosing scopes until it finds a declaration, and finds count in makeCounter. That link is fixed where the inner function is defined, not where it’s called.

Normally, once a function returns, nothing can reach its local variables and the garbage collector reclaims them. A closure is the exception. The returned function holds a reference to the scope it was created in, so that scope stays alive as long as the function does. Call makeCounter() again and you get a second counter with its own count in its own scope:

1
2
3
4
5
const a = makeCounter();
const b = makeCounter();
console.log(a()); // 1
console.log(a()); // 2
console.log(b()); // 1 — separate scope, separate count

Every function you create in JavaScript closes over its surrounding scope, whether you use it or not. Usually nothing outlives the call that created it, so you never notice. It matters when you return the inner function, pass it somewhere, or store it, so it keeps running after its parent scope would otherwise be gone.

How Do You Make Private Variables With a Closure?

The counter already shows the main use: data that only the returned function can touch. Nothing outside makeCounter can read or set count. There’s no property for it and nothing to inspect from the console. You get the data-hiding of a class private field, built from a function call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createAccount(initialBalance) {
  let balance = initialBalance;

  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) throw new Error("insufficient funds");
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    },
  };
}

const account = createAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
console.log(account.balance); // undefined — not reachable

All three methods close over the same balance, so they share it. Outside code can only reach it through the methods you chose to expose. This is the module pattern: wrap private state and a public interface in one function call, return only the interface.

How Do You Memoize a Function With a Closure?

Close over a Map and you have a memoization wrapper, with one cache shared by every call to the returned function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function memoize(fn) {
  const cache = new Map();
  return function (arg) {
    if (cache.has(arg)) return cache.get(arg);
    const result = fn(arg);
    cache.set(arg, result);
    return result;
  };
}

function slowSquare(n) {
  for (let i = 0; i < 1e8; i++) {} // pretend this is expensive
  return n * n;
}

const fastSquare = memoize(slowSquare);
fastSquare(5); // runs the loop, caches 25
fastSquare(5); // returns 25 from the cache, no loop

Both examples follow one shape: call a function that sets up some state, get back one or more functions that keep using it. The cache here never evicts anything, so don’t wrap a function that sees unbounded distinct inputs this way. Python decorators rely on the same mechanism: a decorator closes over the function it wraps and returns a replacement that can still call it.

Why Does var in a Loop Break Closures?

This is the closure mistake almost everyone hits once:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const callbacks = [];
for (var i = 0; i < 3; i++) {
  callbacks.push(function () {
    console.log(i);
  });
}
callbacks.forEach((cb) => cb());
// 3
// 3
// 3

You wanted 0 1 2. You got 3 3 3. var is function-scoped, not block-scoped, so there is exactly one i for the whole loop and all three callbacks close over it. By the time any of them runs, the loop has finished and i is 3.

Declare it with let and the bug disappears:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const callbacks = [];
for (let i = 0; i < 3; i++) {
  callbacks.push(function () {
    console.log(i);
  });
}
callbacks.forEach((cb) => cb());
// 0
// 1
// 2

let is block-scoped, and a for loop with let in its header gives each iteration its own binding. Three separate is. Each callback closes over the one from its iteration.

Before let, the fix was an IIFE that forced a new scope per iteration by passing i in as a parameter:

1
2
3
4
5
6
7
for (var i = 0; i < 3; i++) {
  (function (j) {
    callbacks.push(function () {
      console.log(j);
    });
  })(i);
}

You’ll still find this in older code. Don’t write it today; put let in the loop header.

Can Closures Cause Memory Leaks?

Yes. A closure holds the actual lexical environment its variables live in, for as long as the closure itself is reachable. Attach one to something long-lived, like a DOM event listener, a module-level array, or a cache with no eviction, and everything that environment carries stays in memory too.

The common case: a listener on a DOM node closes over a large object the handler barely uses. The node is removed from the page, the listener is never detached, and both the node and the object stay alive because the closure still references them. Call removeEventListener when the element goes away. And scope closures tightly: if a callback needs one field, close over that field, not the whole object.

The second cost is quieter. A method defined inside a constructor, instead of on the prototype, is re-created with its own closure for every instance rather than shared across all of them. If the inner function captures nothing from the outer one, don’t nest it.

When Should You Use a Closure?

You’ve probably used them already. A debounce or throttle in JavaScript returns a wrapper that closes over a timeoutId persisting between calls, which is how it cancels the previous pending call. Any callback you hand to setTimeout or a promise handler that reads a variable from the scheduling function works the same way: the event loop runs it long after that function returned.

Use a closure on purpose when you need state that persists across calls but shouldn’t be exposed as a property: a counter, a cache, a debounce timer, a function configured once and called later. For a stateless utility that only transforms its arguments, skip the wrapper. There’s nothing to close over, and nesting adds a scope you have to reason about for no gain.