JavaScript Closures Explained With a Backpack Analogy
3 min read
Closures are the interview question that makes self-taught developers sweat — which is ironic, because every one of them uses closures daily without noticing. The concept has a fearsome reputation and a genuinely simple core. One analogy carries the whole thing.
Why closures feel confusing at first
Because the standard definition — "a function bundled with references to its lexical environment" — describes the mechanism instead of the experience. It answers "what is it?" in spec language when the useful question is "what does it let my code do?" The answer to that is one sentence: a function remembers the variables from where it was created, even after that place is gone. Everything else is detail.
The backpack analogy
When a function is created inside another function, it packs a backpack. Into the backpack go the variables that existed around it at birth — not copies of their values, the actual variables. Wherever that function travels (returned from its parent, passed as a callback, stored in an array), the backpack goes with it. When the function runs and needs a variable it doesn't have locally, it reaches into the backpack.
The counterintuitive part: even when the parent function finishes and its execution context is torn down, the backpack survives. The variables inside it live exactly as long as some function is still carrying them.
The classic example: a counter
function createCounter() {
let count = 0; // goes into the backpack
return function () {
count += 1; // reaching into the backpack
return count;
};
}
const counter = createCounter(); // createCounter has now finished. count survives.
counter(); // 1
counter(); // 2
counter(); // 3createCounter ran once and completed. By every intuition from other languages, count
should be gone. But the returned function packed count into its backpack, so it
persists — private, mutable, invisible to the rest of the program. There is no other
reference to count anywhere; the closure is the only way in. You've just seen
encapsulation without classes.
Each backpack is separate, too:
const a = createCounter();
const b = createCounter();
a(); a(); // 2
b(); // 1 — b has its own count in its own backpackTwo calls to createCounter, two independent count variables. This is why closures
are described as functions with private state — it's the closest thing JavaScript had
to instance variables long before class syntax arrived.
Why the backpack persists
JavaScript's garbage collector keeps anything that's still reachable. Normally, a function's local variables become unreachable when it returns — so they're collected. But if an inner function references them and that inner function is still alive, the variables are still reachable through it. The engine literally cannot throw the backpack away while someone's wearing it. (This is also why closures appear in memory-leak stories — a long-lived callback accidentally keeping a huge object in its backpack is a classic.)
You already use closures everywhere
-
Event handlers:
function setupModal(modalId) { const modal = document.getElementById(modalId); closeButton.addEventListener('click', () => { modal.classList.add('hidden'); // modal came from the backpack }); }That arrow function runs minutes after
setupModalfinished — and still knows which modal. Backpack. -
setTimeoutcallbacks: any variable your timeout callback uses from the surrounding function arrives via closure. -
Debounce and throttle: the timer state those utilities remember between calls (full walkthrough here) lives in a closure.
-
Module patterns and hooks: React's
useStatehanding your component its state across renders? Closures, all the way down.
The takeaway
You don't need to learn closures — you've been fluent for years. What the analogy gives you is the ability to reason about them: when a callback "mysteriously" sees a stale value, or a loop's timeouts all print the same number, you now ask the right question — what's in this function's backpack, and when was it packed? That question dissolves ninety percent of closure bugs on contact.