A reflection on closures, memory, and behaviour
For a long time, I told myself I understood JavaScript closures.
I could repeat the definition. I knew they were related to scope. I had even used them in projects without thinking too much about it. But if I am being honest, none of that meant they truly clicked.
And that is okay.
Closures are one of those ideas in JavaScript that do not reward memorisation. They reward time, exposure, and the uncomfortable moments where code behaves consistently while completely violating your expectations.
For me, that moment came in the form of a bug.
The bug that forced me to actually understand closures
The moment closures finally made sense did not come from documentation. It came from chasing behaviour that felt wrong, even though it was technically correct.
I was looping over a list and attaching handlers. Nothing clever. Nothing experimental. Just everyday JavaScript.
But every handler behaved as if it had forgotten which item it belonged to.
I assumed I had made a mistake. Then I assumed some shared state was being overwritten. I added logs. I rewrote the function. The behaviour did not change.
Every handler was seeing the same value.
A simplified version of the problem looked like this:
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
I expected to see 0, 1, and 2.
What I saw was 3, printed three times.
That was the moment I realised I was not dealing with a logic error. I was dealing with a misunderstanding of how JavaScript remembers things.
Each callback was not getting its own copy of the value. They were all holding onto the same variable. By the time the function actually ran, that variable had already changed.
Once I saw that, the behaviour stopped being mysterious. It was predictable. It was even reasonable.
The bug disappeared the moment I stopped asking why JavaScript was doing this and started asking what the function was still connected to.
That was the point where closures stopped being a definition and became a mental model.
Closures are about environments, not syntax
Closures are often introduced as a feature of functions. That framing is convenient, but it is incomplete.
Closures are not something you turn on by writing functions a certain way. They emerge from how JavaScript manages environments over time.
When a function is created, it is associated with the environment in which it was defined. That environment contains bindings, not frozen values. When the function later executes, it resolves those bindings by walking outward through its lexical environments.
A closure exists when that environment remains accessible beyond the lifetime of the original function call.
Why closures persist after functions return
A common misconception is that closures store values. They do not.
What persists is reachability.
If a function still has access to a variable, that variable remains reachable. If it remains reachable, it stays alive in memory. Garbage collection does not care where a variable was declared. It only cares whether something can still reach it.
Once you see closures as a consequence of reachability rather than a special feature, many confusing behaviours stop feeling arbitrary.
Closures explain bugs before they explain features
Most developers encounter closures through bugs long before they use them deliberately.
Asynchronous callbacks, event handlers, and timers all expose the same underlying behaviour. Closures capture variables, not moments in time. Multiple functions can close over the same binding. Delayed execution simply makes this visible.
Understanding this shifts closures from being an interview topic to being a practical debugging tool.
Closures are foundational to encapsulation
Before modern class syntax and private fields, closures were the primary way JavaScript achieved encapsulation.
Even now, they offer something classes cannot always provide cleanly. Privacy without exposure.
A variable that exists only inside a function scope and is accessed only through returned functions is fundamentally inaccessible from the outside. No keyword is required. The structure enforces it naturally.
Closures support intentional design by keeping state local and controlled.
Closures and functional composition
Closures become especially powerful when combined with higher order functions.
Functions that return functions, partially apply arguments, or generate specialised behaviour all rely on closures. This is not an advanced trick. It is a natural extension of how environments work.
Once closures stop feeling mysterious, patterns like currying and composition start to feel obvious rather than clever.
The real cost of closures
Closures are often blamed for performance problems, but that concern is usually misplaced.
The real cost of closures is responsibility.
If you close over large objects, DOM nodes, or long lived references unintentionally, you extend their lifetime. That can lead to memory retention that is difficult to spot.
This is not a reason to avoid closures. It is a reason to understand them well enough to use them deliberately.
A more accurate mental model
After spending time with closures in real code, the most accurate mental model I have found is this:
A closure is not something you create.
A closure is something that remains.
It is the result of:
- lexical scoping
- name resolution
- reference reachability
- delayed or repeated execution
When these conditions align, a closure exists.
Why it is okay that closures take time
Closures sit below the surface of JavaScript. You can write code for years without naming them explicitly. That does not mean you are not using them. It means the language is doing its job.
Understanding closures deeply requires encountering unexpected behaviour, tracing execution mentally, and thinking about memory rather than just output.
That is not beginner work.
If closures took you a while to understand, that is not a failure. It is part of becoming fluent.
They took me a while too.
And that is okay.
Further reading
The ideas in this article were shaped over time by reading and experience. These resources were especially helpful:
JavaScript from Beginner to Professional. For practical reinforcement of function scope and nested behaviour.
Eloquent JavaScript (4th Edition) by Marijn Haverbeke. For building intuition around nested scope and environments.
Clean Code in JavaScript by James Padolsey. For connecting closures to encapsulation, maintainability, and intentional design.
Top comments (0)