The least exposure principle (POLE) encourages us to use block (and function) scoping to limit the scope exposure of variables. Closure builds on this approach: for variables we need to use over time, instead of placing them in larger outer scopes, we can encapsulate them but still preserve access from inside functions, for broader use.
See the Closure
Closure is originally a mathematical concept, from lambda calculus. But I’m not going to list out math formulas or use a bunch of notation and jargon to define it. Instead, I’m going to focus on a practical perspective. We’ll start by defining closure in terms of what we can observe in different behavior of our programs.Closure is a behavior of functions and only functions. If you aren’t dealing with a function, closure does not apply. An object cannot have closure, nor does a class have closure (though its functions/methods might). Only functions have closure.
lookupStudent(..) outer function creates and returns an inner function called greetStudent(..). lookupStudent(..) is called twice, producing two separate instances of its inner greetStudent(..) function.
While greetStudent(..) does receive a single argument as the parameter named greeting, it also makes reference to both students and studentID, identifiers which come from the enclosing scope of lookupStudent(..). Each of those references from the inner function to the variable in an outer scope is called a closure. In academic terms, each instance of greetStudent(..) closes over the outer variables students and studentID.
What Closures Do
Closure allowsgreetStudent(..) to continue to access those outer variables even after the outer scope is finished (when each call to lookupStudent(..) completes). Instead of the instances of students and studentID being garbage collected, they stay around in memory.
Adding Up Closures
Let’s examine one of the canonical examples often cited for closure:addTo(..) function is closing over its own num1 variable (with values 10 and 42, respectively), so those num1’s don’t go away just because adder(..) finishes.
An important detail: closure is associated with an instance of a function, rather than its single lexical definition. Every time the outer
adder(..) function runs, a new inner addTo(..) function instance is created, and for each new instance, a new closure.Live Link, Not a Snapshot
In both examples from the previous sections, we read the value from a variable that was held in a closure. That makes it feel like closure might be a snapshot of a value at some given moment. Indeed, that’s a common misconception. Consider:count variable is closed over by the inner getCurrent() function, which keeps it around instead of it being subjected to GC. The hits() function calls access and update this variable, returning an incrementing count each time.
Common Mistake: Loop Closures
The classic illustration of closure mistakes is defining functions inside a loop:keeps[0]() invocation to return 0. But that assumption stems from thinking of closure as value-oriented rather than variable-oriented.
Something about the structure of a for-loop can trick us into thinking that each iteration gets its own new i variable; in fact, this program only has one i since it was declared with var.
Each saved function returns 3, because by the end of the loop, the single i variable in the program has been assigned 3.
let declaration in a for loop actually creates a new variable for each iteration of the loop:
Common Closures: Ajax and Events
Closure is most commonly encountered with callbacks:onRecord(..) callback is going to be invoked at some point in the future, after the response from the Ajax call comes back. Why then is studentID still around and accessible to the callback? Closure.
Event handlers are another common usage of closure:
label parameter is closed over by the onClick(..) event handler callback. When the button is clicked, label still exists to be used.
Observable Definition
We’re now ready to define closure:Closure is observed when a function uses variable(s) from outer scope(s) even while running in a scope where those variable(s) wouldn’t be accessible.
- Must be a function involved
- Must reference at least one variable from an outer scope
- Must be invoked in a different branch of the scope chain from the variable(s)
The Closure Lifecycle and Garbage Collection (GC)
Since closure is inherently tied to a function instance, its closure over a variable lasts as long as there is still a reference to that function. Consider:onSubmit() with no input on the last line, all event handlers are unsubscribed, and the clickHandlers array is emptied. Once all click handler function references are discarded, the closures are discarded.
Per Variable or Per Scope?
Should we think of closure as applied only to the referenced outer variable(s), or does closure preserve the entire scope chain with all its variables? Conceptually, closure is per variable rather than per scope. Ajax callbacks, event handlers, and all other forms of function closures are typically assumed to close over only what they explicitly reference. But the reality is more complicated than that. Many modern JS engines do apply an optimization that removes any variables from a closure scope that aren’t explicitly referenced. However, there are situations where such an optimization cannot be applied.An Alternative Perspective
There’s another way of thinking about closure that may help deepen the mental models. Instead of thinking about the inner function instance moving to the outer scope via thereturn and assignment, we can envision that function instances actually just stay in place in their own scope environment.
What gets sent to the outer scope is just a reference to the in-place function instance, rather than the function instance itself.
In this alternative model, functions stay in place and keep accessing their original scope chain just like they always could. Closure instead describes the magic of keeping alive a function instance, along with its whole scope environment and chain, for as long as there’s at least one reference to that function instance floating around.
Why Closure?
Now that we have a well-rounded sense of what closure is and how it works, let’s explore some ways it can improve the code structure and organization of an example program. Consider this event handler without closure:makeRequest(..) utility has to retrieve the data-kind attribute from the target button element each time it’s fired. Why couldn’t an event handler remember this value? Let’s use closure:
setupButtonHandler(..) approach, the data-kind attribute is retrieved once at initial setup. recordKind is then closed over by the inner makeRequest(..) click handler.
By placing recordKind inside setupButtonHandler(..), we limit the scope exposure of that variable to a more appropriate subset of the program. Closure lets the inner makeRequest() function instance remember this variable and access it whenever needed.
Closer to Closure
We explored two models for mentally tackling closure:Observational Model
Closure is a function instance remembering its outer variables even as that function is passed to and invoked in other scopes.
Implementational Model
Closure is a function instance and its scope environment preserved in-place while any references to it are passed around and invoked from other scopes.
Benefits to Our Programs
- Improved Efficiency: Closure allows a function instance to remember previously determined information instead of having to compute it each time.
- Better Readability: Closure bounds scope-exposure by encapsulating variable(s) inside function instances, while still making sure the information is accessible for future use.
- Cleaner Code: The resultant narrower, more specialized function instances are cleaner to interact with, since the preserved information doesn’t need to be passed in every invocation.

