Closures in JavaScript: Functions That Remember More Than They Should
An exploration of closures in JavaScript, showing how functions carry their surrounding environment and why they are more about memory than just behavior.
Thereâs a moment in learning JavaScript where functions stop feeling like simple tools and start behaving like something more⌠alive.
You write a function, return another function from it, and somehow â even after the outer function is gone â the inner function still remembers things.
It feels a bit like magic.
But itâs not magic.
Itâs closure.
The Strange Behavior That Starts It All
Consider this:
function outer() {
let count = 0
function inner() {
count++
console.log(count)
}
return inner
}
const fn = outer()
fn()
fn()The surprising part isnât the syntax.
Itâs this question:
Why does inner still have access to count even after outer has finished executing?
If outer is gone, shouldnât count be gone too?
But it isnât.
And thatâs where closures begin.
A Function Doesnât Come Alone
When JavaScript creates a function, it doesnât just store the functionâs code.
It also stores the environment in which the function was created.
So when you return inner, youâre not just returning a function.
Youâre returning:
function + its surrounding variablesThat combination is what we call a closure.
Itâs not a feature you explicitly use.
Itâs something that naturally happens because of how JavaScript handles scope.
Remembering Without Being Told To
A closure allows a function to remember variables from its original scope â even when that scope is no longer active.
That means this:
function greet(name) {
return function () {
console.log("Hello " + name)
}
}
const sayHi = greet("Rishi")
sayHi()Even though greet has already finished, sayHi still knows "Rishi".
Itâs carrying that value with it.
Not because you told it to, but because JavaScript preserved the environment.
Closures as Hidden State
This becomes much more interesting when you realize closures can hold state.
function createCounter() {
let count = 0
return {
increment() {
count++
},
get() {
return count
}
}
}Now:
const counter = createCounter()
counter.increment()
counter.increment()
counter.get() // 2There is no global variable.
No class.
No external storage.
And yet, the value persists.
The count variable lives inside the closure, hidden from the outside world.
This is one of the simplest forms of encapsulation in JavaScript.
Each Closure Lives Its Own Life
Every time you call a function that creates a closure, you get a new, independent environment.
const a = createCounter()
const b = createCounter()
a.increment()
a.increment()
b.increment()
a.get() // 2
b.get() // 1Even though both come from the same function, they do not share state.
Each closure has its own memory.
This is an important shift in thinking:
Closures are not shared containers.
They are individual environments tied to each function instance.
The Subtle Trap: Variables, Not Values
Closures donât capture values.
They capture variables.
This distinction becomes important in loops:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}You might expect:
0
1
2But you get:
3
3
3Because all functions share the same i.
They donât capture the value at each iteration.
They reference the same variable.
Changing var to let creates a new binding per iteration â and fixes the issue.
This is one of the most common closure-related bugs.
Closures Are Everywhere
Once you start noticing closures, you realize theyâre not rare at all.
They appear in:
- event handlers
- callbacks
- timers
- factory functions
- even modern frameworks like Vue and React
Any time a function uses variables from its outer scope, a closure is involved.
Itâs not something you opt into.
Itâs something JavaScript does for you.
A Shift in Perspective
At first, functions feel like isolated blocks of logic.
You give them input, they give you output.
Closures break that model.
They show that functions are not just about behavior.
They are also about memory.
A function can carry context with it.
It can remember where it came from.
And it can use that memory later, even when everything else has moved on.
What Closures Really Are
Itâs tempting to define closures as a technical rule about scope.
But a more useful way to think about them is this:
Closures are how JavaScript allows functions to retain access to their past.
Not in a historical sense, but in a structural one.
They preserve the environment in which they were created.
And that changes how you think about functions entirely.
Theyâre not just actions.
Theyâre actions with memory.