JavaScript Closures Explained Like You're Learning Them for the First Time
Have You Ever Wondered How This Works?
Imagine you have a function:
function createCounter() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const counter = createCounter();
counter();
counter();
counter();
Output:
1
2
3
At first glance this looks strange.
The variable count belongs to createCounter().
Once createCounter() finishes executing, shouldn't count disappear?
Then why does JavaScript still remember it?
The answer is: Closures.
But before understanding closures, let's understand how variables normally behave.
How Variables Normally Work
Consider this function:
function greet() {
let name = "Anshul";
console.log(name);
}
greet();
Output:
Anshul
After the function finishes:
greet();
the variable:
name
is removed from memory because it is no longer needed.
Think of it like this:
Function starts
↓
Variable created
↓
Function ends
↓
Variable removed
This is normal JavaScript behavior.
Now Let's Break the Rules
Look at this code:
function outer() {
let name = "Anshul";
return function inner() {
console.log(name);
};
}
const sayHello = outer();
sayHello();
Output:
Anshul
Something interesting happened.
The function outer() finished executing.
Yet inner() can still access:
name
How?
What JavaScript Actually Does
When JavaScript creates inner(), it notices that the function uses a variable from its parent scope:
name
Instead of deleting that variable, JavaScript keeps it alive.
It creates a hidden connection between:
inner()
+
remembered variables
This connection is called a Closure.
The Simplest Definition
A closure is:
A function that remembers variables from its outer scope even after the outer function has finished executing.
That's it.
No complicated terminology required.
Visualizing a Closure
Step 1:
const sayHello = outer();
Memory:
outer()
name = "Anshul"
Step 2:
return inner;
JavaScript stores:
inner()
|
└── remembers name = "Anshul"
Step 3:
sayHello();
Output:
Anshul
Even though outer() no longer exists.
Real-World Example #1: Counter
Let's build a counter.
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
Output:
1
2
3
Why?
Because the returned function remembers:
count
between executions.
Memory looks like:
counter()
|
└── count = 3
The value survives because of the closure.
Real-World Example #2: Private Variables
Suppose we're building a bank account.
We don't want anyone changing the balance directly.
Bad:
account.balance = 1000000;
Let's hide it.
function createAccount() {
let balance = 1000;
return {
deposit(amount) {
balance += amount;
},
getBalance() {
return balance;
}
};
}
const account = createAccount();
account.deposit(500);
console.log(account.getBalance());
Output:
1500
Trying this:
console.log(account.balance);
Output:
undefined
The variable is protected inside the closure.
Real-World Example #3: Function Factory
Closures allow us to generate customized functions.
function multiplyBy(multiplier) {
return function (number) {
return number * multiplier;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5));
console.log(triple(5));
Output:
10
15
Each function remembers its own value.
double()
remembers multiplier = 2
triple()
remembers multiplier = 3
Why Closures Are Important
Closures are used everywhere in JavaScript:
- Event handlers
- React Hooks
- Debouncing
- Throttling
- Authentication systems
- State management
- Timers
- Function factories
Even if you don't write closures intentionally, you're already using them.
Common Interview Question
What will this print?
function outer() {
let x = 10;
return function () {
console.log(x);
};
}
const fn = outer();
fn();
Answer:
10
Because the returned function remembers x.
Common Mistake
Consider:
for (var i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
Output:
4
4
4
Why?
Because all callbacks share the same variable.
Using let:
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
Output:
1
2
3
Each iteration gets its own closure.
Are Closures Bad for Memory?
Not at all.
But if a closure keeps referencing a huge object, JavaScript cannot remove that object from memory.
Example:
function createHandler() {
const hugeData = new Array(1000000).fill("data");
return function () {
console.log(hugeData.length);
};
}
Since the returned function uses hugeData, it stays in memory.
This can sometimes lead to memory issues if not handled carefully.
Final Thoughts
A closure is simply:
A function remembering variables from the place where it was created.
That's all.
Whenever a function accesses variables from an outer scope and keeps using them later, a closure is created.
Once you understand closures, concepts like:
- Debouncing
- Throttling
- React Hooks
- Memoization
- State Management
become much easier to understand.
The next time someone says "closure," think:
Function + Remembered Variables = Closure
Top comments (0)