JavaScript Closures

A closure lets a function continue to access variables from the lexical scope where that function was created. Closures are fundamental to callbacks and encapsulated state, but references held by long-lived closures can also keep objects reachable for garbage collection.

A closure is formed when a function is created inside a scope and can later access variables from that surrounding lexical scope. The important point is that the function retains access to bindings, even after the outer function has finished executing.

Closures commonly appear with nested functions, callbacks, and event handlers. For example, an inner function can read or modify a variable declared in its outer function, allowing state to persist between calls.

Closures can also provide a simple form of encapsulation. If a variable is declared inside an outer function and only the returned inner functions can access it, code outside that scope cannot directly access the variable.

JavaScript uses lexical scoping, so the accessible outer variables are determined by where a function is defined, not where it is called. This is especially important when callbacks are passed to other functions.

A closure itself is not automatically a memory leak. Garbage collection can reclaim captured data when nothing reachable can access the closure anymore. Problems arise when a long-lived object, timer, event listener, or other reference keeps a closure reachable unnecessarily.

Key points

Interview questions

What is a closure in JavaScript?

A closure is a function together with access to variables from its surrounding lexical scope. The function can continue using those variables even after the outer function has returned. This happens because JavaScript functions retain access to the environment in which they were created. Closures are commonly used for callbacks and private state.

Why does the counter example keep its value between calls?

The returned function closes over the `count` binding created inside `createCounter`. Calling `createCounter` creates that binding once, and the returned function retains access to it. Each call changes the same binding rather than creating a new `count`. Therefore the second call observes the value produced by the first call.

Where are closures commonly used in frontend development?

Closures are common in event handlers, asynchronous callbacks, timers, and functions that create other functions. They allow callbacks to retain configuration or state from the code that created them. They are also useful for implementing module-like private state. In frontend applications, developers should consider how long those callbacks remain reachable, especially when they capture large objects or component-related data.

Does a closure copy the values of variables it captures?

Not generally. A closure retains access to the relevant lexical bindings, so later changes to a captured variable can be observed by the closure. This differs from thinking of the closure as simply storing a snapshot of every variable's value. The exact behavior can also depend on whether the variable is mutable and on how the surrounding code creates bindings.

How can closures contribute to memory problems?

A closure can keep captured objects reachable for as long as the closure itself remains reachable. For example, a long-lived timer or event listener can retain a callback that references a large object graph. Garbage collection cannot reclaim reachable objects merely because their original function has returned. The practical fix is to remove unnecessary long-lived references, such as cleaning up listeners or timers when their work is finished.