JavaScript Closures and Scope Explained
A closure is a function that remembers the variables from the place where it was created, even after that outer function has returned. Closures are behind callbacks, React hooks, module patterns and much more.
Lexical scope
const greeting = 'Hello';
function outer() {
const name = 'Asha';
function inner() {
console.log(`${greeting}, ${name}`); // inner can see outer's variables
}
inner();
}
Scope is decided by where code is written, not where it's called.
A closure keeps variables alive
function createCounter() {
let count = 0; // private: nothing outside can touch it
return {
increment: () => ++count,
current: () => count,
};
}
const counter = createCounter();
counter.increment();
counter.increment();
counter.current(); // 2
Function factories
const formatter = (currency) => (amount) =>
new Intl.NumberFormat('en-IN', { style: 'currency', currency }).format(amount);
const inr = formatter('INR');
const usd = formatter('USD');
inr(1500); // '₹1,500.00'
The classic loop bug
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 3, 3, 3 - one shared 'var i'
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 0, 1, 2 - a new 'i' each iteration
}
Stale closures in React
useEffect(() => {
const id = setInterval(() => setCount(count + 1), 1000); // always sees the first 'count'
return () => clearInterval(id);
}, []);
// Fix: use the updater form, which receives the latest value
setCount((c) => c + 1);
Memory
Closures keep their captured variables in memory. That's usually fine, but a long-lived listener that captures a huge object keeps it alive. Remove listeners and clear timers when you're done.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.