JavaScript · Advanced Concept
JavaScript Closures
One of the most powerful and misunderstood concepts in JavaScript. Once you understand closures — everything clicks.
I remember staring at closure code for hours – confused why a function could still "remember" variables after its parent function had finished running. Then it clicked. Closures are everywhere in JavaScript — in event handlers, callbacks, modules, and React hooks. This is the guide I wish I had.
What is a Closure?
A closure is a function that remembers the variables from its outer scope — even after the outer function has finished running.
In other words: an inner function always has access to the variables of its parent function. This "remembering" is called a closure.
JavaScript — Basic Closure
function outer() {
let message = "Hello from outer!";
function inner() {
// inner can access outer's variable
console.log(message);
}
return inner; // return the function
}
const myFunc = outer(); // outer() runs and finishes
myFunc(); // "Hello from outer!" — still works!
// WHY? Because inner() closes over message
// Even though outer() finished — message is remembered
Real World Example — Counter
The most classic closure example — a counter with private state. The count variable is hidden inside but the returned functions can still access it:
JavaScript — Counter with Closure
function createCounter() {
let count = 0; // private variable
return {
increment() {
count++;
console.log(`Count: ${count}`);
},
decrement() {
count--;
console.log(`Count: ${count}`);
},
getCount() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // Count: 1
counter.increment(); // Count: 2
counter.decrement(); // Count: 1
console.log(counter.count); // undefined — private!
console.log(counter.getCount()); // 1
Factory Functions with Closures
You can use closures to create multiple independent instances — each with their own private state:
JavaScript — Factory Function
function createPlayer(name) {
let score = 0; // each player has own score
return {
addPoints(points) {
score += points;
console.log(`${name}: ${score} points`);
},
getScore() {
return score;
}
};
}
const player1 = createPlayer("Waheed");
const player2 = createPlayer("Ali");
player1.addPoints(10); // Waheed: 10 points
player2.addPoints(5); // Ali: 5 points
player1.addPoints(20); // Waheed: 30 points
// Each player has independent score!
console.log(player1.getScore()); // 30
console.log(player2.getScore()); // 5
Where Closures Are Used
| Use Case |
Why Closures? |
Example |
| Private Variables |
Hide data from outside |
Bank account balance |
| Factory Functions |
Create multiple instances |
createUser(), createCounter() |
| Event Handlers |
Remember context |
Button click handlers |
| Callbacks |
Keep data alive |
setTimeout, fetch |
| React Hooks |
useState uses closures |
useState, useEffect |
Pro Tips
Pro Tip #1 — Every function is a closure
In JavaScript, every function automatically closes over the variables in its outer scope. You don't have to do anything special — it just happens automatically.
Pro Tip #2 — Closures create private variables
JavaScript has no built-in private keyword (before ES2022). Closures were the standard way to create truly private variables that can't be accessed from outside.
Pro Tip #3 — Watch memory usage
Closures keep variables in memory as long as the function exists. If you create many closures holding large data, it can cause memory issues. Always clean up when done.
Quick Cheatsheet
// 1. Basic closure
function outer() {
let x = 10;
return () => console.log(x); // remembers x
}
// 2. Private variable
function makeSecret(secret) {
return () => secret; // only way to get secret
}
// 3. Memoization with closure
function memoize(fn) {
const cache = {};
return (n) => {
if (cache[n]) return cache[n];
cache[n] = fn(n);
return cache[n];
};
}
// 4. Once function — runs only one time
function once(fn) {
let called = false;
return (...args) => {
if (!called) {
called = true;
return fn(...args);
}
};
}
const init = once(() => console.log("Initialized!"));
init(); // "Initialized!"
init(); // nothing — already called
Want daily web dev tips?
Follow Muhammad Waheed Asghar for daily JavaScript and CSS tips!