Conceptly
← All Concepts
📦

Closure

FunctionsA function that remembers the variables from the scope where it was created

A closure is a function that retains access to the variables of its enclosing scope even after the outer function has finished executing. The inner function does not copy those variables -- it holds a live reference to them, so reads and writes affect the same binding that the outer scope created.

Architecture Diagram

🔍 Structure

Dashed line animations indicate the flow direction of data or requests

Why do you need it?

Functions normally execute and then disappear, taking their local variables with them. But there are cases where you need a piece of state that outlives the call that created it, yet does not belong on a global object where anything could read or overwrite it. Without closures, your options narrow down to either polluting global state or using a class to wrap a single mutable field. Closures offer a third path: the outer function defines the variable, the inner function captures it, and the variable stays alive precisely as long as the inner function is reachable -- no more, no less.

Why did this approach emerge?

Closures are not a new idea. Languages like Lisp supported them decades before mainstream programming discovered them. JavaScript brought closures into widespread use partly because its event-driven model demanded functions that could remember context across asynchronous boundaries. When a button click handler needs to remember which item was selected, or when a timer callback needs to update a counter defined earlier in the code, closures make that possible without passing state through every intermediate layer. As functional programming patterns spread from JavaScript into Python, Kotlin, Swift, and Rust, closures became a near-universal building block of modern code.

How does it work inside?

A closure is built in two steps. First, an outer function defines one or more local variables. Second, it defines and returns an inner function that references those variables. When the outer function returns, the runtime notices that the inner function still holds a reference to the outer variables and keeps them alive on the heap rather than discarding them with the stack frame. Every subsequent call to the inner function reads and writes the same binding. If the outer function is called again, a brand-new closure with its own independent set of captured variables is created. That is why two calls to a counter factory produce two counters that count separately.

Boundaries & Distinctions

Closures and classes both encapsulate state alongside behavior. A class is explicit about grouping -- you name the fields, define the methods, and create an instance. A closure is implicit -- state lives inside the function's scope and is accessible only through the returned function. Classes suit larger, more structured models with multiple methods and explicit types; closures are lighter and fit well when the encapsulated behavior is narrow, like a single counter or a one-off event handler. In a language with first-class functions, closures often eliminate the need for a class whose sole purpose is holding one private field. Closures and module-level variables both keep state alive beyond a single function call. The difference is visibility and ownership. A module variable is accessible to every function in that module; a closure variable is accessible only to the function that closed over it. When state only matters to one function, a closure keeps that constraint enforced by the structure of the code rather than by convention.

Trade-off

Closures make private state and factory functions easy to write, and they compose naturally with higher-order functions. The cost is that captured references can unintentionally extend the lifetime of large objects. If a closure captures a reference to a large data structure and the closure itself is long-lived, the data structure cannot be garbage-collected even if nothing else references it. In event-heavy environments this is a real source of memory leaks. A second cost is readability: deeply nested closures can be hard to follow because the captured variables are not visible at the call site. The practical rule is that closures are excellent for narrow, well-scoped state but need careful review when they are long-lived or when they close over expensive objects.

When should you use it?

Closures are most useful when you need per-instance private state without the overhead of a full class, or when you need to pass a function that already carries its configuration with it. Counter factories, debounce and throttle wrappers, partial application helpers, and React hooks all rely on closures as their core mechanism. The design signal is: if you find yourself passing the same extra argument through multiple layers just so a callback can see it, a closure is likely the cleaner solution. If you notice memory growing steadily as closures accumulate, inspect what large objects each closure is keeping alive and whether those references can be cleared earlier.

Counter and state encapsulation -- keeping private state inside a function without exposing it as a global variableEvent handlers -- capturing loop variables so each handler correctly refers to its own valueFactory functions -- producing functions pre-configured with different settings from the same templateMemoization -- caching expensive computation results by closing over a cache object