Closures
Understand what a closure actually is, why functions remember the scope they were created in, and how to use closures for data privacy, function factories, and memoization.
Reading Time
18 min
Lesson
Lesson 36 of 48
What is a Closure?
A closure is what you get when a function keeps access to the variables from its lexical scope, even after the outer function that created that scope has already finished running. In plain terms: a function remembers where it was born. Every function in JavaScript forms a closure over its surrounding scope the moment it's created — you don't have to do anything special to enable it. What makes closures interesting is what happens when the inner function outlives the outer one: normally you'd expect the outer function's variables to be thrown away once it returns, but if an inner function still references them, they stick around in memory instead of being garbage collected.
The Simplest Possible Closure
Console Output
Click “Run” to see the console output here.
The Classic Counter Example
The most common way closures are introduced is with a counter. Each call to makeCounter() creates a brand-new count variable and a brand-new increment function that closes over it. Because the two counters returned below each close over their own separate count, incrementing one has zero effect on the other — they are not sharing any state, even though they were created by the exact same function.
A Counter Built with Closures
Console Output
Click “Run” to see the console output here.
Closures in Loops: the var vs. let Pitfall
One of the most frequently asked interview questions about closures involves a loop with setTimeout(). If you declare the loop counter with var, every callback closes over the same single variable, because var is function-scoped — there's only one i for the entire loop, and by the time the timers fire, the loop has already finished and i holds its final value. Switching to let fixes this, because let is block-scoped: each iteration of the loop gets its own fresh binding of i, so each callback closes over a different variable.
var vs. let Inside a Loop
Console Output
Click “Run” to see the console output here.
Fixing the var Version Without let
Before let existed, developers fixed this by wrapping the loop body in an immediately invoked function expression (IIFE) that took i as a parameter, creating a new function scope — and therefore a new closure — on every iteration. let simply does the same thing automatically at the language level.
Practical Use: Data Privacy
Because a closure keeps its variables inaccessible from the outside world, closures are one of the main ways JavaScript achieves private state. The balance variable below can only ever be read or changed through the functions returned by createAccount() — there is no way to reach in and set balance directly from outside, since it was never attached to the returned object itself.
Private State with a Closure
Console Output
Click “Run” to see the console output here.
Practical Use: Function Factories
A function factory is a function that returns other, customized functions. Each returned function closes over the arguments the factory was called with, so you get a family of related functions without repeating logic. This is closures used for configuration rather than for hiding state.
Building Functions with a Factory
Console Output
Click “Run” to see the console output here.
Practical Use: Memoization and Currying
Closures also power memoization — caching the result of an expensive calculation so repeated calls with the same input skip the work the second time around. The cache object below lives inside the closure created by memoize(), invisible to outside code, but remembered across every call to the returned function. The same idea underlies currying, where a function returns a chain of smaller functions, each one closing over the arguments collected so far.
A Simple Memoized Function
Console Output
Click “Run” to see the console output here.
Where Closures Show Up in Real Code
- Private variables and encapsulation (like the bank account example)
- Function factories that generate customized functions
- Memoization and caching layers
- Event handlers and callbacks that need to remember context
- The module pattern, used to avoid polluting the global scope
Gotcha: Closures Capture Variables, Not Values
A closure captures a reference to the variable itself, not a snapshot of its value at the time the inner function was created. If the outer variable changes after the closure is created but before it's called, the closure will see the updated value — this is exactly why the var loop example above logs 4 three times instead of 1, 2, 3. Closures are live links to their variables, not frozen copies.
A Mental Model That Helps
Think of a closure as a backpack a function carries around. Whenever a function is created, it packs a backpack with references to every variable it might need from its surrounding scope. Wherever that function is later called — even in a completely different part of the program — it still has that backpack with it.