Ever wondered how some JavaScript functions seem to “remember” variables even after they’ve finished running? That’s closures at work — one of JavaScript’s most powerful (and 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, you’ll not only understand closures — you’ll master them with real-world examples, debugging tricks, and challenges. Ready to level up? 🚀
“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?
Imagine you have a secret vault, and only a special key can open it. This key isn’t available to the world — it’s kept safe inside the vault.
Closures work the same way: an inner function keeps a reference to the variables of its outer function, even after the outer function has finished executing.
🔹 Why Are Closures Useful?
✅ Data Privacy (Preventing Global Pollution)
✅ State Persistence (Keeping Values Even After Execution)
✅ Function Factories (Returning Customized Functions)
✅ Event Listeners, Async Operations, and Module Patterns
Real-World Analogy: The Secret Vault 🔐1️⃣ The vault is created → A function is declared.
2️⃣ A key is stored inside the vault → A variable is kept inside the function.
3️⃣ Only a specific person can access it → An inner function has access to the variable, but the outside world doesn’t.
💻 Example: Understanding Closures Step by Step
🔹 Without Closures: Variables Don’t Persist
function counter() {
let count = 0;
count++;
return count;
}
console.log(counter()); // 1
console.log(counter()); // 1 (Resets every time)
🔴 Problem: Every time counter()
is called, count
is reset.
✅ Closures: Keeping Values Across Calls
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
✅ Why is this better?
- 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 — you’re leveraging the power of closures.
What JavaScript concept should we break down next? 🚀
Top comments (1)
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