Closures In Depth
Closures In Depth
Persistent lexical state, data encapsulation, and lifecycle management in JavaScript.
The Traveler and the Permanent Backpack
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In JS, closures are created every time a function is created, at function creation time.
Lexical Environment Retention
When an outer function executes, it creates a local Variable Environment. When it returns an inner function that references those variables, V8 retains the environment in the Memory Heap.
Garbage Collection Protection
Because the returned inner function retains an active reference to the outer scope variables, the engine garbage collector recognizes they are reachable and preserves them.
Live Bindings, Not Static Snapshots
A closure does not copy values at return time; it holds live references. Mutating a closed-over variable updates it for all functions sharing that lexical environment.
Data Encapsulation and Privacy
Before ES2022 private class fields (`#`), closures were the premier pattern for creating strictly private, unmodifiable variables in JavaScript.
Encapsulating private state and solving the classic loop closure trap:
// 1. Private Counter through Closure
function createBankAccount(initialBalance) {
let balance = initialBalance; // Private state
return {
deposit(amount) { balance += amount; return balance; },
withdraw(amount) {
if (amount > balance) return "Insufficient funds";
balance -= amount;
return balance;
},
getBalance() { return balance; }
};
}
const account = createBankAccount(100);
account.deposit(50);
console.log("Balance:", account.getBalance()); // 150
console.log("Direct access:", account.balance); // undefined!
// 2. The Classic Loop Trap (Solved with let)
const functions = [];
for (let i = 0; i < 3; i++) {
// 'let' creates a new binding and scope per loop iteration
functions.push(() => i);
}
console.log(functions.map(fn => fn())); // [0, 1, 2]
Balance: 150
Direct access: undefined
[ 0, 1, 2 ]
The var in for-loop Trap
The Risk: Using `for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); }` logs `3, 3, 3` because `var` is function-scoped and all callbacks share one mutated variable.
The Fix: Use `let i = 0`, which allocates a new lexical binding for every iteration of the loop, or use an IIFE.
Accidental Memory Leaks
The Risk: Holding closures in long-lived event listeners or global arrays while referencing large DOM trees or objects prevents GC.
The Fix: Remove event listeners when unmounting components and nullify unused closure references.
- ✓ Closure = Function + its surrounding Lexical Environment.
- ✓ Enables true private variables and encapsulation.
- ✓ Variables are live references, not copies or values frozen in time.
- ✓ `let` in loops creates a separate closure per iteration; `var` shares one variable.
- ✓ Be mindful of memory leaks when holding references to large objects in closures.