Closures in JavaScript
- A closure is a function bundled with its lexical environment — it carries its outer variables with it.
- Closures survive the return of their outer function — the closed-over variables stay alive.
- var in a for loop shares one variable across all loop iterations — use let to get a new binding per iteration.
A closure is a function that retains access to variables from its outer (enclosing) scope even after the outer function has returned. The inner function closes over those variables — it carries them with it. Closures are how JavaScript implements private state, factory functions, memoization, and event handlers with context.
How Closures Work
function makeCounter(start = 0) { let count = start; // this variable lives in makeCounter's scope return { increment() { count++; }, decrement() { count--; }, value() { return count; } }; // makeCounter returns and its stack frame is gone // but count survives because the returned object closes over it } const counter = makeCounter(10); counter.increment(); counter.increment(); counter.decrement(); console.log(counter.value()); // 11 // Two independent counters — each has its own count const a = makeCounter(0); const b = makeCounter(100); a.increment(); a.increment(); b.increment(); console.log(a.value()); // 2 console.log(b.value()); // 101 — b has its own count
2
101
The Classic Loop Closure Trap
One of the most common JavaScript interview questions. The surprise: var-declared loop variables are shared across all closures created in the loop.
// The trap — using var const fns = []; for (var i = 0; i < 3; i++) { fns.push(() => console.log(i)); } fns[0](); // 3 — not 0! fns[1](); // 3 fns[2](); // 3 // Why? var has function scope, not block scope. // All three closures share the SAME i variable. // By the time they run, i is already 3. // Fix 1: use let (block-scoped — a new binding per iteration) const fns2 = []; for (let i = 0; i < 3; i++) { fns2.push(() => console.log(i)); } fns2[0](); // 0 ✓ fns2[1](); // 1 ✓ fns2[2](); // 2 ✓ // Fix 2: IIFE to capture the current value (pre-ES6) const fns3 = []; for (var i = 0; i < 3; i++) { fns3.push(((capturedI) => () => console.log(capturedI))(i)); } fns3[0](); // 0 ✓
3
3
0
1
2
0
Practical Pattern — Memoization
Closures are perfect for memoization: a function that caches its results so expensive calculations are only done once.
function memoize(fn) { const cache = new Map(); // closed over by the returned function return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { console.log(`Cache hit for ${key}`); return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }; } const expensiveSqrt = memoize((n) => { console.log(`Computing sqrt(${n})...`); return Math.sqrt(n); }); console.log(expensiveSqrt(16)); // Computing sqrt(16)... → 4 console.log(expensiveSqrt(16)); // Cache hit for [16] → 4 console.log(expensiveSqrt(25)); // Computing sqrt(25)... → 5
4
Cache hit for [16]
4
Computing sqrt(25)...
5
🎯 Key Takeaways
- A closure is a function bundled with its lexical environment — it carries its outer variables with it.
- Closures survive the return of their outer function — the closed-over variables stay alive.
- var in a for loop shares one variable across all loop iterations — use let to get a new binding per iteration.
- Closures enable private state, factory functions, memoization, and partial application.
- Every function in JavaScript is a closure — it closes over at least the global scope.
Interview Questions on This Topic
- QWhat is a closure in JavaScript?
- QWhy does a loop with var produce unexpected results when closures are involved?
- QHow would you implement a counter using closures?
Frequently Asked Questions
Do closures cause memory leaks?
They can. If a closure holds a reference to a large object and the closure itself is kept alive (e.g., as an event listener that is never removed), the large object cannot be garbage-collected. The fix is to remove event listeners when they are no longer needed, or to break the reference by setting variables to null.
What is the difference between a closure and a callback?
A callback is a function passed as an argument. A closure is a function that remembers its outer scope. Most callbacks are closures — when you pass an arrow function to addEventListener or setTimeout, it typically captures variables from the surrounding scope, making it a closure. They are not mutually exclusive.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.