Closures for Data Encapsulation
A closure can keep mutable state private by returning functions that retain access to variables from their defining scope. This creates a small public interface while preventing outside code from directly accessing the enclosed state.
A closure is created when a function retains access to variables from its lexical scope after the outer function has finished executing. The inner function can continue reading or modifying those captured bindings when it is called later.
For encapsulation, an outer function creates private variables and returns operations that can access them. Code outside the closure receives the functions, not the variables themselves, so the state cannot be changed directly through ordinary property access.
Each invocation of the factory function can create a separate closure with its own state. This makes closures useful for independent counters, caches, configuration objects, and other stateful utilities.
Closures provide encapsulation through lexical scope rather than a special private-state mechanism. Modern JavaScript also has class private fields using `#`, so the appropriate technique depends on the API design and language features being targeted.
Key points
- Captured variables remain accessible to returned functions after the outer function returns.
- The enclosed state is not directly accessible from code outside the closure.
- Calling the factory function creates a new private state for that instance.
- Expose only the operations that should be part of the public interface.
- Closures use lexical scope to achieve encapsulation.
Interview questions
How can a closure be used to create private state in JavaScript?
An outer function creates a variable and returns one or more inner functions that use it. Those inner functions retain access to the variable because of lexical scoping. Code outside the outer function cannot directly reference that variable by name. The returned functions therefore form the public interface to the private state.
Why does each call to a closure-based factory usually have independent state?
Each invocation of the factory function creates a new execution context with its own local bindings. Functions returned from that invocation close over those particular bindings. A later invocation creates a different set of bindings and therefore a different private state. The two closure instances can change their state independently.
What is the difference between a closure and a normal object property for encapsulation?
A normal object property is part of the object's externally accessible property surface unless additional mechanisms restrict it. Closure state instead lives in the lexical environment and is not exposed as an object property. Functions returned by the closure can selectively read or modify that state. This allows the API to control which operations are available to callers.
What memory consideration should you mention when using closures for private state?
A closure keeps references to variables in its lexical environment as long as reachable functions need them. If those variables reference large objects, the objects can remain reachable and therefore unavailable for garbage collection. This is not inherently a leak; it becomes a problem when closures are retained unintentionally or for too long. Developers should pay attention to listener lifetimes, caches, timers, and other long-lived references.