Closures
Something Unexpected
Before any explanation, run this and read the output carefully.
makeCounter has finished executing. Its local variables should be gone — that's how functions normally work. And yet count is still there, still updating, still remembered by the inner function.
This is a closure. The inner function closed over the variable count and carries it with it wherever it goes.
Why This Happens
When a function is created, it doesn't just get its code — it also gets a reference to the scope it was created in. That scope contains the variables visible at that moment.
When makeCounter returns the inner function, the inner function takes the scope with it. As long as the inner function is alive, the variables in that scope stay alive too.
count is completely inaccessible from outside. You cannot read it, you cannot set it directly, you cannot even see it. The only way to interact with it is through the three methods you were given. This is private state — the same concept that takes other languages years of class syntax to achieve.
Each Closure Gets Its Own Private Copy
This is the part that surprises people most. Run this carefully:
countA and countB each got their own count. They share no memory. Every call to makeCounter creates a brand new closure with its own private copy of start and count.
Factory Functions
A function that creates and returns other functions is called a factory function. Closures are what make them useful — each returned function carries its own private configuration.
makeMultiplier(2) doesn't run the multiplication. It creates a specialised function that remembers factor = 2 forever. You get a reusable, configurable tool.
The Classic Trap — And Why It Surprises Everyone
This is the most famous closure bug in JavaScript. Guess the output before you run it.
All three print 5. Not 0, 1, 2. Why?
var is not block-scoped. There is only one i variable, shared by the entire loop. By the time any function runs, the loop has finished and i is already 5. All five closures share the same i, and they all see its final value.
The fix is let, which creates a new binding for each iteration of the loop:
With let, each iteration gets its own separate i. Each closure captures its own copy. This is one of the main reasons let replaced var in modern JavaScript.
Closures Remember Arguments, Not Just Variables
The greeting parameter behaves exactly like a local variable — it's closed over and remembered for as long as the returned function exists.
A Practical Pattern: Memoization
Closures are how you build a cache — a function that remembers its previous results so it never computes the same thing twice.
cache is completely private. It cannot be tampered with from outside. The memoized function is the only one who can read and write it. This is a real pattern used in production applications — React's useMemo hook is built on exactly this idea.
What You Have Learned
A closure is a function that carries its birth environment with it — the variables and parameters that existed when it was created.
The key ideas:
- A function returned from another function keeps a live reference to the outer scope — not a copy, a reference
- Every call to the outer function creates a new, independent closure with its own private state
varin loops shares one variable;letgives each iteration its own — this is whyvarcauses the classic closure bug- Factory functions use closures to create configurable, specialised functions
- Private state is not a class feature in JavaScript — it's a closure feature
- Patterns like memoization, event handlers, and React hooks are all closures underneath
In the next lesson: Callbacks and Higher-Order Functions — you have already been passing functions to map, filter, and forEach. Now you will understand exactly what is happening, and how to write functions that receive other functions as arguments, unlocking the full power of JavaScript's functional style.