Closures are the reason inner functions "remember" variables from outer functions long after the outer function has returned. Think of a closure like a backpack — when a function is born, it packs up all the variables it can see in its surrounding scope and carries that backpack wherever it goes, even long after the outer function has finished.
They power module patterns, memoization, once(), and almost every callback you've ever written. Understanding the memory model behind closures changes how you write JavaScript.
Prerequisites: JS Foundations #1 — Variables & Scope, JS Foundations #2 — this
1. What is a Closure?
A closure is created when a function retains access to variables in its outer lexical scope even after that outer function has finished executing. In JavaScript, every function is a closure.
Let's walk through what happens step by step:
outer()is called — it creates a local variablemessageand definesinner()inner()is returned — but it still "remembers"messagefromouter's scopeouter()finishes — normally its variables would be destroyed, but...inner()still holds a reference tomessage, so the JS engine keeps it alive on the heap- When
fn()is called later, it can still accessmessageas ifouter()never ended
Loading editor...
outer() returned. Its stack frame is gone. But message still lives — on the heap — because inner still references it. The garbage collector won't touch message as long as fn exists.
2. The Scope Chain in Closures
Closures don't capture a snapshot — they hold a live reference to the outer variable. If the variable changes before the inner function runs, the inner function sees the latest value:
Loading editor...
Every closure sees 3 because var i is function-scoped — there's only one i, and by the time the closures run, the loop has finished and i is 3.
The fix with let — let creates a fresh binding per iteration:
Loading editor...
Or using an IIFE to create a new scope for each iteration (the pre-ES6 fix):
Loading editor...
3. Private State with Closures
The most practical use of closures: encapsulating private data. No class, no fields — just functions.
Loading editor...
This is the module pattern — every createCounter() call creates a new, independent closure with its own private count:
Loading editor...
4. Closure for Memoization
Cache expensive function results so repeated calls with the same input are instant:
Loading editor...
5. once() — Ensure a Function Runs Only Once
A useful pattern using closures to prevent a function from being called more than once:
Loading editor...
6. Function Factories
Closures can produce specialized functions — think of it as "creating a function from a template":
Loading editor...
This is the core of currying and partial application — covered in depth in a later article.
7. The Loop + setTimeout Closure Bug
A common task: log 1, 2, 3 at one-second intervals. Let's see what goes wrong and why.
Loading editor...
The loop races through in microseconds. By the time the first setTimeout fires (500ms later), i is already 4 (the value that made the loop stop). All three callbacks see that same 4.
Loading editor...
Loading editor...
All three approaches produce the same output. The key insight: closures don't capture values; they capture variable references. let solves it by creating a fresh variable per iteration. IIFE solves it by creating a fresh scope with a copy of the value.
8. Releasing Closures for Garbage Collection
As long as a reference to the inner function exists, the closure's variables stay alive. To free that memory, set the reference to null:
Loading editor...
This matters when you attach closures to DOM elements that may be removed, or when you cache closures in long-lived data structures.
9. Closure Performance Tip
Closures keep variables alive. If you capture a large object but only need a small part, you keep the entire object in memory:
Loading editor...
Key Takeaways
- A closure is a function + its lexical environment — it retains access to outer-scope variables even after the outer function returns.
- Closures hold live references, not snapshots — the classic
for+var+setTimeoutbug. - Closures enable private state without classes: counters, caches, once(), module patterns.
- Every function in JavaScript is a closure. The question is just what it closes over.
- Be mindful of memory — closing over a large object keeps the whole object alive.
Next: JS Foundations #4 — Prototypes & Inheritance — __proto__ vs prototype, what new really does, and how class extends is sugar over the prototype chain.
