Closures are the most important concept in JavaScript. They power React hooks, module patterns, and event handlers. Let me show you the deep mechanics.
What is a Closure?
A closure is a function that remembers the variables from its lexical scope, even after that scope has finished executing.
function createCounter() {
let count = 0; // This variable is "closed over"
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
// count is private — no way to access it directlyMemory Implications
Closures keep their outer scope alive. This can cause memory leaks if you're not careful.
// Memory leak example
function attachHandler() {
const hugeData = new Array(1000000).fill("x"); // 1M strings
const element = document.getElementById("button");
element.addEventListener("click", () => {
console.log(hugeData.length); // hugeData stays in memory!
});
}
// Fix: only close over what you need
function attachHandler() {
const hugeData = new Array(1000000).fill("x");
const length = hugeData.length; // Extract needed value
element.addEventListener("click", () => {
console.log(length); // Only length is retained
});
}The Classic Loop Problem
// Bug: all callbacks print 5
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100); // 5, 5, 5, 5, 5
}
// Fix 1: let (block scoping)
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2, 3, 4
}
// Fix 2: IIFE (closure creates new scope)
for (var i = 0; i < 5; i++) {
((j) => setTimeout(() => console.log(j), 100))(i);
}Closure Patterns
Memoization
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const expensiveCalc = memoize((n) => {
console.log("Computing...");
return n * n;
});
expensiveCalc(5); // Computing... 25
expensiveCalc(5); // 25 (cached)Module Pattern
const API = (() => {
let baseURL = "";
const headers = {};
return {
configure(url, token) {
baseURL = url;
headers.Authorization = `Bearer ${token}`;
},
get: (path) => fetch(baseURL + path, { headers }),
post: (path, body) => fetch(baseURL + path, { method: "POST", headers, body: JSON.stringify(body) })
};
})();