Menu

JavaScript Closures: How They Work and Why They Matter

A closure is a function that remembers the variables around it. Here's how closures work in JavaScript, with runnable examples and real use cases.

A Closure Is a Function That Remembers

Every time you define a function in JavaScript, it quietly keeps a link to the variables around it. When the function runs later — even somewhere completely different — it can still see those variables. That's a closure.

The shortest demo:

index.js
Output
Click Run to see the output here.

makeGreeter runs, returns an inner function, and exits. You'd expect its local variable name to disappear — the function is done. But the inner function still uses name, so JavaScript keeps it alive. greetAda remembers "Ada". greetBoris remembers "Boris". Two closures, two separate remembered values.

Lexical Scope Is Where Closures Come From

The rule behind closures is called lexical scope: a function can see variables from the place it was written, not the place it's called. "Lexical" just means "based on where in the source code it sits."

index.js
Output
Click Run to see the output here.

show logs "I'm outside", not "I'm inside caller". It was written next to the top-level outer, so that's the one it sees. Calling it from somewhere that happens to have its own outer doesn't matter.

Closures are just lexical scope that outlives the outer function. The variable doesn't go away because someone still has a reference to it.

Each Call Gets Its Own Closure

A fresh call to the outer function creates fresh variables, and any inner function returned from that call remembers those variables. This is why greetAda and greetBoris above didn't clash.

The classic example is a counter:

index.js
Output
Click Run to see the output here.

a and b each hold their own count. Nothing outside the returned function can touch those variables — count is completely private. That's not a language feature we turned on; it falls out of how closures work.

Private Variables Without Classes

Because the enclosed variables are only reachable through the returned function, you can use closures to build small objects with truly private state:

index.js
Output
Click Run to see the output here.

balance isn't a property on the returned object — it lives in the closure. The only way to read or change it is through the methods you exposed. Classes with #private fields can do this too, but the closure version predates them by decades and still shows up all over the ecosystem.

The Classic Loop Gotcha

Closures trip people up most often inside loops. Watch what happens with var:

index.js
Output
Click Run to see the output here.

You'd expect 0, 1, 2. You get 3, 3, 3. Here's why: var is function-scoped, so there's only one i for the whole loop. All three closures captured the same variable, and by the time they run, the loop has finished and i is 3.

Switch to let:

index.js
Output
Click Run to see the output here.

Now it logs 0, 1, 2. let is block-scoped — each iteration of the loop creates a fresh binding for i, so each closure captures its own value. This is the single biggest reason to prefer let over var.

Closures Capture Variables, Not Values

A subtle but important point: a closure holds on to the variable, not a snapshot of its value at the time the function was defined.

index.js
Output
Click Run to see the output here.

printMessage reads message when it runs, not when it was created. If you want a snapshot, copy the value into a local variable first — which is effectively what let does inside a for loop.

A Common Real-World Pattern: Once

Here's a tiny utility that uses a closure to make sure a function only runs a single time:

index.js
Output
Click Run to see the output here.

called and result are private state that lives as long as the returned function does. No global flag, no extra object. This pattern — small helper, private state, closure — is one of the most useful things JavaScript does.

A Word on Memory

A closure keeps its captured variables alive as long as something still references the closure. That's usually what you want, but if you attach a closure to something long-lived (like a DOM event listener or a global cache) and it captures something big, that thing can't be garbage collected until the closure goes away.

function attach() {
    const hugeData = new Array(1_000_000).fill("...");
    document.addEventListener("click", () => {
        console.log(hugeData.length);
    });
}

As long as the listener is attached, hugeData stays in memory. Remove the listener (or avoid capturing what you don't need) and the reference clears. You don't need to micromanage this — just know that closures and memory are linked.

What You Take Away

  • A closure is a function plus the variables it saw when it was defined.
  • Every call to an outer function creates a fresh set of variables for its inner closures.
  • Closures give you private state without needing classes.
  • Inside loops, use let so each iteration gets its own binding.
  • Closures capture the variable itself, not the value at creation time.

Next: The this Keyword

Closures handle the variables around a function. The next piece is what a function is called on — which in JavaScript is controlled by this, and behaves very differently from the captured variables we just covered.

Frequently Asked Questions

What is a closure in JavaScript?

A closure is a function that remembers the variables from the scope where it was defined, even after that outer scope has finished running. Every function in JavaScript is technically a closure — the term usually comes up when a function is returned or passed elsewhere and still uses variables from its original scope.

Why are closures useful?

They let a function carry private state along with it. You get data that's tied to a function without needing a class or a global variable. Common uses include counters, once-only callbacks, memoized helpers, and hiding implementation details behind a small API.

Why do closures behave strangely inside loops with var?

var is function-scoped, so every iteration shares the same variable. Closures created in the loop all reference that one variable, which ends up at the final value by the time they run. Use let instead — it's block-scoped, so each iteration gets its own binding.

Learn to code with Coddy

GET STARTED