Closures
Understand the idea
A click handler needs to remember how many times it has run, but resetting a local on every click keeps the count stuck at one.
A backpack that stays with the function
Why keep anything after a function returns? A click handler may need a counter between clicks, or a search helper may need a chosen delay. Making all that data global would let unrelated code change it. The backpack lets the function keep just the surroundings it needs.
Two calls, two sets of bindings
The outer call has two jobs: establish its local bindings and create the inner function. The inner function can use those bindings because it was defined inside that scope. Returning the function gives the caller a way to do that work later, without exposing a direct name for the private count.
Creating a function and calling it are separate events. makeCounter creates the inner increment function and returns it without calling it. The next binding then holds that function. Only next() performs an increment. A second call to makeCounter would create a second count and another backpack; it would not reset the first.
| Operation | Which state it uses | What happens |
|---|---|---|
| makeCounter() | A new outer call’s count | Creates a function with its own retained access |
| First next() | The first counter’s existing count | Changes 0 to 1 |
| Second next() | That same count | Changes 1 to 2 |
| Another makeCounter() | A separate count | Creates independent state starting at 0 |
| Two inner functions from one outer call | A shared outer binding | Can observe each other’s updates |
What the backpack actually keeps
A binding can survive after its creating call has left the active call stack. It remains available while a reachable function still needs access to it. This retained access is what allows a second click to continue where the first left off.
The backpack is a mental model for retained access, not a literal copy of every value. If two inner functions share one outer binding, a change made through one is visible to the other. This explains why a closure can track changing state instead of always reporting its original value.
Useful private state
This makes closures useful for private counters, configured helpers, and handlers attached to a specific item. Two returned functions from the same outer call can deliberately cooperate through one count. Two separate outer calls have separate local bindings, so creating another counter does not reset the first.
Follow creation separately from use
Closures do not require returning a function; event handlers and can close over surrounding bindings too. They are useful, but they can also keep large reachable longer than expected. Remove unused handlers and timers so an otherwise finished feature can release the state it no longer needs.
Trace the example in two phases. During setup, count starts at zero and next receives the returned function. Later, the first next() changes that same count to one; the second changes it to two. count is not directly readable outside makeCounter, but increment still has access. If that distinction takes a second read, that is the useful part to slow down on.
Mental model
Outer function is running
next()
Inner function
A new lexical environment is created for this invocation.
01 / 07Call makeCounter()
Live example
makeCounter runs once. The returned runs twice and keeps using the same count, so the second result is 2.
Your console output appears here.
Guided challenge
Keep the remembered count
Increment the retained binding on each call instead of assigning the same value every time.
Your console output appears here.
EXPECTED CONSOLE OUTPUT
1 2
Hints 0/2
Use all hints or make 3 failed runs (0/3).
Free sandbox
Make a variation, test an assumption, or break the example on purpose. This draft is saved separately.
Your console output appears here.
Quiz
A quick check for understanding. Retry as often as you like; your learning path stays open.
Ready to call this one understood?
Mark this lesson complete to earn 150 XP. You can always revisit it.