Ever wondered how some JavaScript functions seem to “remember” variables even after they’ve finished running? That’s referred to as closures— one of JavaScript’s most powerful (but little confusing) features.
In this guide, you’ll learn:
🔹 How closures preserve data in functions.
🔹 Why they’re essential for state management and encapsulation.
🔹 Common pitfalls (and how to avoid them).
🔹 How closures power React Hooks, event handlers, and async code.
🔹 How they compare to similar features in Python, Java, and C++.
By the end of this article, we’ll make sure you understand closures correctly— with short examples, real-world analogy, and a quick challenge plus free and paid resources for more knowledge. Ready to dive in? 🚀
“A fool thinks himself to be wise, but a wise man knows himself to be a fool.” — William Shakespeare
Closures might seem simple, but they can confuse even experienced developers. Let’s break them down properly.
🧠 Introduction: What Are Closures and Why Do They Matter?
Simply put: In JavaScript closures are functions (called inner functions) defined inside other functions (referred to as outer functions) which give them a special power — the access to the outer variables and scope even after the outer function has finished executing!
Now before, continuing with the technical side, imagine you have a vault, and only a special key can open it. This key isn’t available to the world— instead only a trusted person can have it!
Even if the vault is locked and becomes inaccessible to others, the trusted person still holds the key and can access the vault’s contents whenever needed.
Let’s now map these concepts to closures!
Closures work the same way as the previous analogy: the inner function (which is the trusted person) retains access to the variables (contents) of its outer function (vault), even after the outer function has finished executing (i.e not accessible to everyone); inner function can still have access the variables.
Analogy Breakdown 🔐
1️⃣ The vault is created → A function is defined.
2️⃣ Contents are stored inside the vault → Variable are declared inside the defined function.
3️⃣ Only a trusted person can access the vault → Only an inner function; defined inside the outer function; has access to the variables, but the outside code doesn’t.
If you have a better analogy, please leave it in the comments :)
🔹 Why Are Closures Useful?
Closures are useful because they make it very easy to implement:
✅ Data Privacy (Preventing Global Pollution)
✅ State Persistence (Keeping Values Even After Execution)
✅ Function Factories (Returning Customized Functions)
✅ Event Listeners, Async Operations, and Module Patterns
💻 Example: Understanding Closures Step by Step
Let’s now try to understand closures with an example!
🔹 Without Closures: Variables Don’t Persist
Let’s define a simple function in JavaScript with a variable and call it twice:
function counter() {
let count = 0;
count++;
return count;
}
console.log(counter()); // 1
console.log(counter()); // 1 (Resets every time)
🔴 Behavior: Every time counter() is called, count is reset. This is an expected behavior and nothing is wrong about it, but what If we need some extra features without so much code! In our case, we need to implement the intended functionality of a counter!
✅ Closures: Keeping Values Across Calls
Let’s suppose we need to create a counter; here is how we can do it very easily with closures:
function createCounter() {
let count = 0; // Private variable
return function () {
count++;
return count;
};
}
const myCounter = createCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
console.log(myCounter()); // 3
✅ By simply defining a function inside another function; we get:
- Data Persistence:
count
retains its value across calls. - Encapsulation:
count
is protected; no direct access. - Reusability: Multiple independent counters can be created.
⚠️ Beginner Pitfalls & How to Overcome Them
🔴 1. Expecting closures to copy variables instead of reference them
Issue: Many beginners think closures store a copy of the variable at the time of function creation. Instead, they keep a reference to the original variable.
Example: Unexpected **setTimeout**
behavior
for (var i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Logs: 4, 4, 4 (instead of 1, 2, 3)
✅ Fix: Use let
to create a block-scoped variable.
for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Correctly logs: 1, 2, 3
🔎 Think Like a Compiler: How Closures Work Internally
Closures are possible because of lexical scoping:
- When a function is created, it remembers the scope where it was declared.
- Even after the outer function finishes execution, its variables are not destroyed if they are referenced inside a closure.
Example Visualization:
function outer() {
let a = 10;
return function inner() { console.log(a); }
}
const fn = outer();
fn(); // Logs 10 (Even though `outer()` has finished)
📌 Memory is only freed when there are no references to the closure.
⚡ Closures in the Wild: Real-World Applications
🔹 1. Avoiding Global Variables (Data Privacy)
function bankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
console.log(`New Balance: ${balance}`);
},
withdraw(amount) {
if (amount > balance) {
console.log("Insufficient funds!");
} else {
balance -= amount;
console.log(`New Balance: ${balance}`);
}
},
getBalance() {
return balance;
}
};
}
const myAccount = bankAccount(100);
myAccount.deposit(50); // New Balance: 150
myAccount.withdraw(30); // New Balance: 120
console.log(myAccount.getBalance()); // 120
✅ Here, **balance**
is private and only accessible through methods!
🔹 2. Closures in React’s **useState**
(Modern Web Development)
function Counter() {
const [count, setCount] = React.useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
✅ Closures allow **setCount**
to remember the previous state.
🔄 Closures in Other Languages (If You Come From…)
🔹 Python (Similar: Inner Functions & **nonlocal**
)
Closures in Python work similarly using inner functions and **nonlocal**
:
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
my_counter = counter()
print(my_counter()) # 1
print(my_counter()) # 2
🔹 Java (No Direct Equivalent, but Similar to Anonymous Classes)
Java doesn’t have true closures, but anonymous classes with final variables behave similarly.
🔹 C++ (Lambdas Capture State)
std::function<int()> createCounter() {
int count = 0;
return [=]() mutable { return ++count; };
}
✅ Closures in C++ are captured in lambdas, just like JavaScript functions.
🛠️ If You Understand This, Try… (Challenge Section)
🔹 Challenge: Implement a function that only runs once using closures.
function once(fn) {
let ran = false;
return function (...args) {
if (!ran) {
ran = true;
return fn(...args);
}
};
}
const init = once(() => console.log("This runs only once!"));
init(); // Runs ✅
init(); // Doesn't run ❌
✅ Used in JavaScript libraries like Lodash!
📖 Learn More: Best Books & Resources on Closures
📚 Free Resources
- MDN Docs on Closures — Official Mozilla documentation.
- JavaScript.info: Closures — A well-explained article with examples.
💰 Paid Books for Deep Understanding
- “You Don’t Know JS: Scope & Closures” by Kyle Simpson — The ultimate guide to closures and scope.
- “JavaScript: The Good Parts” by Douglas Crockford — Covers closures as a fundamental JS feature.
🎯 Conclusion: Why Closures Are Essential
Closures allow functions to “remember” data even after execution. They help in data privacy, function factories, and efficient state management.
Next time you use setTimeout, event listeners, or create custom counters, remember to leverage closures! These "backpacks" of data make your functions more powerful by letting them carry around the variables they need, even after the outer function is gone.
What JavaScript concept should we break down next? 🚀
Top comments (3)
Very in depth knowledge of this, thanks for this amazing article, I already knew what closures are and how it works but this even enhanced my knowledge about closures
Thanks for your comment! Glad it was helpful for you!
I enjoyed reading this article, thanks!