Introduction
1 . What Exactly Is a Closure?
2 . Why Closures Exist – The Lexical Environment
3 . Closures in Real Life – Common Use Cases
4 . Closures and Loops – The Classic Gotcha
5 . Closures in Modern JavaScript
6 . Pros and Cons of Closures
7 . How Closures Affect Memory
8 . Advanced Patterns with Closures
9 . Closures in Interviews
Conclusion
If you’ve been writing JavaScript for a while, you’ve probably stumbled upon the term closure. It’s one of those concepts that can feel mysterious at first, like an inside joke all senior developers get but no one fully explains.
Closures are everywhere in JavaScript, callbacks, event handlers, private variables, currying, memoization, you name it. In fact, you can’t write modern JavaScript without using closures, whether you realize it or not.
In this article, we’ll completely demystify closures. By the end, you’ll not only understand them, you’ll be able to use them with confidence, write cleaner code, and even impress your interviewer.
A closure is created when a function “remembers” variables from its outer scope—even after that scope has finished executing.
Think of a closure as a backpack: a function carries with it all the variables from the scope it was defined in, no matter where it’s called later.
function outer() {
let counter = 0;
function inner() {
counter++;
console.log(counter);
}
return inner;
}
const myFunction = outer();
myFunction(); // 1
myFunction(); // 2
myFunction(); // 3
Even though outer() has finished executing, the inner() function remembers counter. This memory is the closure.
Closures exist because of lexical scoping in JavaScript.
Lexical scope means that a function’s scope is determined by where it’s physically written in the code.
When a function is created, it forms a relationship with the scope in which it was declared.
The scope chain is preserved even when the function is executed in a completely different place.
Key takeaway: The reason closures work is because JavaScript doesn’t just execute code—it also remembers the environment in which a function was created.
a) Private Variables
Closures let you hide data from the outside world, similar to private fields in OOP.
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
console.log(`New balance: $${balance}`);
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
console.log(`New balance: $${balance}`);
} else {
console.log('Insufficient funds');
}
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(100);
account.deposit(50); // New balance: $150
console.log(account.getBalance()); // 150
Here, balance is private, it can’t be accessed directly, only through the returned methods.
b) Function Factories
Closures can create customized functions.
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
c) Event Handlers
Closures help keep track of state in UI interactions.
function setupButton(id) {
let count = 0;
document.getElementById(id).addEventListener('click', function() {
count++;
console.log(`Button clicked ${count} times`);
});
}
setupButton('myButton');
Even after setupButton is done, the event listener remembers count
d) Once Functions
Run a function only once.
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
result = fn.apply(this, args);
called = true;
}
return result;
};
}
const init = once(() => console.log("Initialized!"));
init(); // Initialized!
init(); // (nothing happens)
Closures inside loops often trip developers up.
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i); // Logs 4, 4, 4
}, 1000);
}
Why? Because var is function-scoped, and by the time the callbacks run, i is 4.
Fix:
for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 1000); // 1, 2, 3
}
for (var i = 1; i <= 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
Closures aren’t just for old-school JS, they’re still essential today:
React Hooks use closures to remember state across renders.
Node.js modules use closures to keep variables private.
Functional programming techniques like currying and composition rely heavily on closures.
Example in React:
function Counter() {
const [count, setCount] = React.useState(0);
function increment() {
setCount(prev => prev + 1); // closure over `prev`
}
return <button onClick={increment}>{count}</button>;
}
✅ Pros:
⚠️ Cons:
Closures can keep variables alive in memory longer than expected.
function bigDataClosure() {
const hugeArray = new Array(1000000).fill('data');
return function() {
console.log(hugeArray.length);
};
}
const fn = bigDataClosure();
// hugeArray is still in memory because fn references it
Tip: If you no longer need a closure, set its reference to null to allow garbage collection.
a) Currying
Breaking a function into multiple single-argument functions.
function curryAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curryAdd(1)(2)(3)); // 6
b) Memoization
Caching results for performance.
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) return cache[key];
const result = fn(...args);
cache[key] = result;
return result;
};
}
const slowSquare = num => {
console.log('Computing...');
return num * num;
};
const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // Computing... 25
console.log(fastSquare(5)); // 25 (from cache)
Closures are a must-know interview topic. Expect questions like:
Explain closures in simple terms.
Write a function that maintains state between calls.
Create a private counter.
Why might closures cause memory leaks?
How do closures differ in var vs let?
Closures are not just a quirky JavaScript feature—they’re a core part of how the language works. They enable:
When you understand closures, you unlock the ability to write JavaScript that’s not only functional but elegant.
Closures might feel mysterious at first, but they’re just functions carrying a “backpack” of variables from their original scope. Once you start recognizing and intentionally using them, you’ll see them everywhere, and you’ll be a much stronger developer for it.
Software Engineer
Senior Software Engineer