What is a closure?
A function that remembers.
A closure is a function that captures variables from its outer scope and keeps them alive, even after the outer function has returned. It's the mechanism behind private state, callbacks, factory functions, and most of the language's patterns. Every function in JavaScript is technically a closure, but the term usually refers to the case where an inner function outlives its outer scope. This page covers how closures work, where they show up, and the loop bug everyone hits.
Short answer: A closure is a function plus the scope it was defined in. When you return an inner function from an outer function, the inner function remembers the outer function's variables — even after the outer function has finished. This lets you keep state private (nobody outside can access it), create factory functions (each with their own state), and use callbacks that remember variables from where they were defined. The classic gotcha: closures in a loop with var share the same variable. Fix: use let.
✅ The pattern
An inner function that remembers its outer scope.
function createGreeter(name) {
const greeting = 'Hello';
// Inner function — captures name and greeting
return function () {
return greeting + ', ' + name;
};
}
const greetAlice = createGreeter('Alice');
const greetBob = createGreeter('Bob');
console.log(greetAlice()); // "Hello, Alice"
console.log(greetBob()); // "Hello, Bob"
// createGreeter has finished — but the returned
// functions still have access to name and greeting.
// That's a closure.
What happens step by step:
1. createGreeter('Alice') is called.
2. A new scope is created with name = 'Alice'
and greeting = 'Hello'.
3. The inner function is defined — it references
name and greeting from the outer scope.
4. The inner function is returned.
5. createGreeter finishes — its scope would
normally be garbage collected.
6. But the inner function still holds a reference
to the scope. So it stays alive.
When you call greetAlice():
→ The inner function runs.
→ It looks up greeting and name.
→ They're not in its own scope.
→ It looks up to the outer scope (still alive).
→ Returns "Hello, Alice".
✅ Each call to createGreeter creates a new closure.
✅ greetAlice and greetBob have separate state.
✅ The captured variables are private — no outside
code can access greeting or name directly.
✅ The private counter
State you can change — but not access.
function createCounter() {
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
value() { return count; }
};
}
const counter = createCounter();
counter.increment();
counter.increment();
counter.increment();
console.log(counter.value()); // 3
counter.decrement();
console.log(counter.value()); // 2
// The count variable is private.
// There's no counter.count property.
// The only way to change it is via the methods.
What you can't do: ❌ counter.count = 10; → Doesn't affect the internal count. ❌ console.log(counter.count); → undefined. ❌ Delete counter.count; → No effect. ✅ counter.increment(); ✅ counter.decrement(); ✅ counter.value(); This is the module pattern — private state, public methods. It's how JavaScript implemented encapsulation before classes and private fields. The three inner functions all close over the same count variable. Each call to createCounter creates a new count — the three methods share it. If you call createCounter() twice, you get two independent counters, each with its own count. The closures don't interfere.
✅ The loop gotcha
The one that trips up every JavaScript developer.
// ❌ The classic bug
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // 3, 3, 3 — not 0, 1, 2
}, 100);
}
// ✅ The fix — use let
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // 0, 1, 2
}, 100);
}
With var:
Loop iteration 1 → callback closes over i
Loop iteration 2 → callback closes over SAME i
Loop iteration 3 → callback closes over SAME i
After loop → i = 3
100ms later:
All three callbacks run
All see i = 3
Output: 3, 3, 3
❌ All closures share the same i.
❌ By the time they run, i is 3.
With let:
Loop iteration 1 → callback closes over own i = 0
Loop iteration 2 → callback closes over own i = 1
Loop iteration 3 → callback closes over own i = 2
100ms later:
Callbacks run in order
Each sees its own i
Output: 0, 1, 2
✅ Each iteration gets a new binding.
✅ Each closure captures a different value.
let's block scope creates a new variable
per iteration. This is why let exists.
The var workaround (for legacy code):
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(function () { console.log(j); }, 100);
})(i);
}
IIFE creates a new scope per iteration.
Works, but verbose. let is cleaner.
Don't use closures to store state that should be garbage collected. A closure keeps its captured variables alive as long as the closure itself is alive. If you attach a closure to a long-lived object — a global event listener, a DOM element, a module-level variable — the captured variables stay in memory even after you're done with them. This is a common source of memory leaks in single-page applications. The fix: remove event listeners when the component unmounts, use WeakMap for caches that should be garbage collected with their keys, and be aware of what your closures capture. Modern JavaScript engines optimize this — but they can't help with closures you're still holding.
⚠️ Common mistakes
The closure mistakes that cost real time.
var in a loop with closuresAll callbacks share the same variable. Fix: use let.
They capture the variable, not the value. Fix: use let, or capture the value explicitly.
Event listeners keep variables alive. Fix: remove listeners on unmount.
Scope is where variables exist. Closure is when a function remembers them after the outer scope ends.
Inner variable with the same name hides outer. Fix: rename or use different scopes.
forEach gives each callback its own value; for with var doesn't. Fix: use let or forEach.
Even top-level functions capture their module scope. Fix: understand it, don't fear it.
thisArrow functions and regular functions handle this differently in closures. Fix: use arrow functions for callbacks that need the outer this.
❓ Frequently asked
What is a closure in JavaScript?
A closure is a function that remembers the variables from its outer scope, even after the outer function has returned. When you define a function inside another function, the inner function closes over (captures) the outer function's variables. Those variables stay alive as long as the inner function exists — even if the outer function has finished executing.
Why do closures matter?
Three reasons. (1) Private state: a closure can hold variables that no outside code can access. (2) Callbacks: event handlers, setTimeout callbacks, and promise handlers all use closures. (3) Factory functions: a function that returns another function, each with its own captured state. Closures are how JavaScript implements lexical scope.
What is the loop closure bug?
When you create closures inside a loop and use var for the loop variable, all the closures share the same variable. By the time they run, the variable has its final value. Example: for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 100); } — logs 3, 3, 3, not 0, 1, 2. Fix: use let instead of var.
How do I create private variables with closures?
Use a function that defines variables and returns functions that access them. Example: function createCounter() { let count = 0; return { increment: () => count++, decrement: () => count--, value: () => count }; }. The count variable is private — no outside code can access it directly.
Do closures cause memory leaks?
Closures can hold onto variables longer than necessary. A closure captures its entire outer scope, including variables it doesn't use. If a closure is attached to a long-lived object, the captured variables stay in memory. The fix: remove event listeners when done, use WeakMap/WeakSet for caches, and be aware of what your closures capture.
What is the difference between closure and scope?
Scope is the set of variables that are accessible from a given point in the code. Closure is a function that captures variables from its outer scope and keeps them alive. Scope exists everywhere; closure is a specific case where a function remembers its scope.
🔗 Continue learning
Next in this series:
this in JavaScript?this depending on how a function is called