Introduction
Closures are one of the most powerful and frequently asked JavaScript concepts in interviews. They allow functions to "remember" variables from their outer scope, even after that outer function has finished executing.
Once you understand closures, you'll better understand callbacks, event listeners, React hooks, private variables, and many JavaScript patterns.
What Is a Closure?
A closure is a function that has access to variables from its outer lexical scope, even after the outer function has returned.
Example:
function greet(name) {
return function () {
console.log(`Hello ${name}`);
};
}
const sayHello = greet("Yacine");
sayHello();
Output:
Hello Yacine
Although greet() has already finished executing, the inner function still remembers the value of name.
Lexical Scope
Closures are possible because JavaScript uses lexical scope.
const message = "Hello";
function sayHello() {
console.log(message);
}
sayHello();
Output:
Hello
Functions can access variables declared in their parent scope.
A Simple Closure Example
function counter() {
let count = 0;
return function () {
count++;
return count;
};
}
const increment = counter();
console.log(increment());
console.log(increment());
console.log(increment());
Output:
1
2
3
The variable count is preserved between function calls.
Private Variables
Closures allow you to create private state.
function createUser(name) {
let balance = 0;
return {
deposit(amount) {
balance += amount;
},
getBalance() {
return balance;
},
getName() {
return name;
},
};
}
const user = createUser("Alice");
user.deposit(100);
console.log(user.getBalance());
Output:
100
The balance variable cannot be accessed directly.
Closures with Event Listeners
function createButton(message) {
return function () {
alert(message);
};
}
button.addEventListener("click", createButton("Welcome!"));
Each event handler remembers its own message.
Closures in Loops
Incorrect:
for (var i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
Output:
4
4
4
Correct:
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
Output:
1
2
3
let creates a new binding for each iteration.
Function Factory
Closures make it easy to generate specialized functions.
function multiply(multiplier) {
return function (number) {
return number * multiplier;
};
}
const double = multiply(2);
const triple = multiply(3);
console.log(double(10));
console.log(triple(10));
Output:
20
30
Closures in React
Closures are everywhere in React.
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
return (
<button onClick={increment}>
{count}
</button>
);
}
The increment function closes over the current count value.
Common Use Cases
Closures are commonly used for:
- Data privacy
- Event handlers
- Callbacks
- Timers
- Function factories
- Memoization
- React Hooks
- Custom hooks
- Module patterns
Common Mistakes
Forgetting closures keep references
function test() {
let value = 1;
return () => value;
}
The closure doesn't copy the variable—it keeps a reference to it.
Memory leaks
Closures can unintentionally keep large objects in memory if they are no longer needed.
function largeData() {
const items = new Array(1000000).fill("data");
return () => items.length;
}
Avoid retaining unnecessary references.
Best Practices
- Use closures for encapsulation.
- Keep captured variables small.
- Prefer
letandconstovervar. - Avoid unnecessary nested functions.
- Be mindful of memory usage.
- Use closures intentionally, not everywhere.
Closures are a fundamental feature of JavaScript. They allow functions to retain access to variables from their lexical scope, enabling powerful patterns like private state, callbacks, event handlers, and function factories.
Whether you're building applications with JavaScript, React, Next.js, or Node.js, understanding closures will help you write cleaner, more maintainable, and more predictable code.