/* javascript */

Closures Are Just Backpacks

the sutra

A closure does not remember the world. It remembers only what it packed.

Most explanations of closures start with scope chains and lexical environments. Useful, eventually — but not where intuition starts.

Here is a smaller model: when a function is created, it packs a backpack. Inside the backpack go only the outer variables it actually references in its body. Nothing else from the surrounding scope comes along for the ride.

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

const counter = makeCounter();
counter(); // 1
counter(); // 2

The returned function packed count in its backpack before makeCounter finished running. makeCounter itself is long gone by the time you call counter() — but the backpack survives, because the inner function is still holding it.

This is why closures are the backbone of things like debounce functions, private state in modules, and React's useState. Each of those is really just: pack a backpack, hand someone a function, let them carry it around.

The bug this model prevents: loops. This is the classic gotcha —

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// logs 3, 3, 3 — not 0, 1, 2

var is function-scoped, so there is only one i, and only one backpack, shared by all three timeouts. Switch to let and each loop iteration gets its own fresh i — its own backpack — so you get 0, 1, 2 as expected.

Next time a closure confuses you, stop asking "what scope is this in." Ask instead: what did this function actually pack before it left home?

more in javascript