Closure

On this page 7

A closure is a function together with a reference to the lexical environment in which it was created, which lets the function read and often modify variables from that enclosing scope even after the outer function has returned. In functional programming, closures are the mechanism that turns functions into first-class values able to carry private state.

Closures make higher-order functions, callbacks, partial application and currying practical. They are native to JavaScript, Python, Ruby, Swift, Kotlin, C#, Go, Rust, Haskell and most other modern languages, and they matter because they decide what data a function captures, for how long, and by value or by reference.

How a closure captures its environment

Closures rely on lexical scoping: a variable name refers to the binding visible where the code is written, not where it is called. When an inner function refers to a variable of its outer function, that variable becomes a free variable of the inner function, and the runtime must keep it alive as long as the inner function exists.

Implementations do this by moving captured variables out of the call stack into a heap-allocated environment. JavaScript engines create a context object, Python stores captured variables in cell objects listed in the function’s __closure__ attribute, and C# compilers generate a hidden class whose fields hold the captured variables. The function value then becomes a pair: code pointer plus environment.

Because the environment is shared, several closures created in the same call see the same variables. A counter factory that returns both an increment and a read function works precisely because both closures reference one shared binding rather than separate copies.

Capture semantics across languages

The most consequential detail of closures is whether they capture a variable or its value, and languages answer this differently. The table summarizes the behavior in widely used languages.

LanguageCapture defaultCan modify captured variableNotable rule
JavaScriptBy reference to the bindingYeslet in loops creates a fresh binding per iteration
PythonBy reference, late bindingOnly with nonlocalLoop variables are read at call time
JavaValue of effectively final variablesNoLambdas reject reassigned locals
C++Explicit capture listWith reference capture[=] copies, [&] references
RustBorrow or move, inferredWith mutable borrowmove transfers ownership into the closure
SwiftStrong referenceYesCapture lists such as [weak self] break cycles
How common languages capture variables

Explicit control, as in C++ capture lists and Rust’s move keyword, trades convenience for predictable lifetimes and memory use. Implicit reference capture, as in JavaScript and Python, is concise but produces the late-binding surprises described below.

Closure is often used as a synonym for lambda or anonymous function, but the concepts answer different questions. A lambda is a syntax for writing a function without a name; a closure is a runtime value that carries captured state. A named function can be a closure, and a lambda that uses no outer variables captures nothing.

ConceptDefining propertyRelation to closures
LambdaFunction without a nameBecomes a closure if it uses outer variables
Higher-order functionTakes or returns functionsUsually returns closures
Pure functionSame input gives same output, no side effectsA closure over immutable data can stay pure
Object with methodsState plus behaviorClosures are an equivalent encapsulation tool
Partial applicationFixes some arguments in advanceImplemented by capturing those arguments
Closure compared with neighboring concepts

The link to purity and immutability is important in functional programming. A closure that captures only immutable values behaves like a pure function with preset parameters. A closure that captures and mutates shared state carries hidden side effects, which makes reasoning and testing harder.

Typical uses of closures

Closures appear wherever behavior must be configured once and executed later. The most common patterns are listed below.

  • Callbacks and event handlers that remember the context in which they were registered.
  • Function factories such as a makeMultiplier(3) that returns a tripling function.
  • Partial application and currying, which fix arguments step by step.
  • Data privacy, as in the JavaScript module pattern, where captured variables are unreachable from outside.
  • Memoization, where a wrapper captures a cache keyed by arguments.
  • Decorators and middleware that wrap a function with logging, timing or authorization.

In React, hooks rely heavily on closures, and stale closures are a well-known class of bug: an effect or callback keeps reading the state value from the render in which it was created. Dependency arrays and functional state updates exist to control exactly this capture.

Pitfalls and how to avoid them

Most closure bugs come from capturing more, or for longer, than intended. The table links common symptoms to their causes and remedies.

SymptomCauseRemedy
All callbacks print the last loop valueLate binding of a shared loop variableUse block-scoped variables or bind the value as a parameter
Memory grows in a long-running appClosure keeps a large object graph aliveCapture only needed fields, remove listeners
Object never deallocated in SwiftStrong reference cycle through selfUse [weak self] or [unowned self]
UI shows outdated stateStale closure from an earlier renderUpdate dependencies or read current state
Race on shared captured dataConcurrent closures mutate one bindingCapture immutable copies or synchronize access
Common closure problems

Performance overhead is usually small, typically one heap allocation per closure creation, but creating closures in tight loops of hot code can add measurable garbage collection pressure. In such paths, hoisting the closure out of the loop is a simple and effective fix.

In simple terms

A closure is a function that remembers where it came from. Think of a note written in a meeting that still makes sense later because it was clipped to the meeting agenda. Even after the meeting ends, the note keeps its context.

In code, a closure keeps access to the variables that were around when it was created. This memory is what lets a small function carry its own settings or private data without anyone else being able to change them.

A practical example

A front-end developer needs to limit how often a search box sends requests to a server while a user types. She writes a debounce function that takes a function and a delay of 300 milliseconds and returns a new function. The returned function is a closure that captures a private timer variable shared across calls.

Each keystroke calls the closure, which clears the previous timer and starts a new one, so the request fires only after the user pauses. No global variable is needed, and each search box gets its own independent timer because each call to debounce creates a separate environment. Request volume to the search endpoint drops by roughly an order of magnitude while the interface still feels immediate.