Closures
Overview
A closure is a mechanism where a function keeps access to the scope, or lexical environment, in which it was defined.
Normally, variables inside a function scope can be garbage-collected after the function finishes. However, if an inner function references variables from an outer scope, those variables are kept alive instead of being discarded.
In the example below, when makeCounter() is called, the execution context for makeCounter is created in memory.
An increment function object is then created, and at that point it captures a reference to count. This is where the closure is formed.
Explanation
Let's look at how closures work in a little more detail. Here, we can think about closures in two phases: "Definition" and "Call".
First, procedure P is defined by using a closure. At definition time, procedure P also stores a reference to context D.
In this discussion, a context means a reference x such as a variable that holds a specific state, and a context can store multiple pieces of reference information.
At this point, P's environment, meaning its set of references, can be described as being closed over by its definition context.
When P is called from context C, it uses the reference from context D to perform its work.

Purpose
The purpose of closures is to give functions state and scope. From a design perspective, this can be summarized in four points:
- Preserving state
- Encapsulation
- Function customization
- Keeping context in asynchronous code and callbacks
Use Cases
Closures can be applied in several practical situations.
1. Private Variables (Encapsulation)
Closures can express something similar to private fields in object-oriented programming. They prevent private variables from being accessed directly from the outside, which improves state safety and maintainability.
function createUser(name) { let _name = name; // private return { getName() { return _name; }, setName(newName) { _name = newName; }, }; } const user = createUser("Alice"); console.log(user.getName()); // Alice user.setName("Bob"); console.log(user.getName()); // Bob console.log(user._name); // undefined (cannot access directly)
2. Function Factories
Closures make it possible to create functions that keep configuration inside them. A function created by createMultiplier(2) remembers 2 permanently.
function createMultiplier(multiplier) { return function (value) { return value * multiplier; }; } const double = createMultiplier(2); const triple = createMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15
3. Memoization
Closures can give expensive functions a memory. If the same arguments are passed again, the function can skip computation and return a cached result. useMemo and lodash's _.memoize are based on this kind of pattern internally.
function memoize(fn) { const cache = {}; return function (...args) { const key = JSON.stringify(args); if (cache[key]) { console.log("cache hit"); return cache[key]; } console.log("compute"); const result = fn(...args); cache[key] = result; return result; }; } function slowAdd(a, b) { return a + b; } const fastAdd = memoize(slowAdd); console.log(fastAdd(1, 2)); // compute -> 3 console.log(fastAdd(1, 2)); // cache hit -> 3