A closure is a function that retains access to its outer lexical scope even after that outer function has finished executing. Closures are one of JavaScript's most powerful and frequently used features, enabling patterns like data encapsulation, factory functions, and callbacks.
A closure is created every time a function is defined inside another function and the inner function references variables from the outer function's scope. JavaScript uses lexical scoping, meaning a function's scope is determined by where it is written in the source code, not where it is called. When the outer function returns, the inner function 'closes over' the variables it needs, keeping them alive in memory. This bundling of a function together with its surrounding state is called a closure.
Closures allow you to create private variables that cannot be accessed or modified from outside a function, simulating encapsulation without classes. They are the foundation of many JavaScript patterns including module patterns, partial application, and memoization. Without closures, techniques like maintaining state between event handler calls or building factory functions would be far more verbose and fragile.
When JavaScript executes a function, it creates an execution context with a local variable environment. If an inner function references a variable from that environment, the JavaScript engine attaches a reference to that environment — called the closure's [[Environment]] slot — to the inner function object. Even after the outer function's execution context is popped off the call stack, the variable environment persists in memory as long as the inner function holds a reference to it. Garbage collection only removes that environment when no closure references it anymore.
Consider a counter factory: `function makeCounter() { let count = 0; return function() { return ++count; }; }`. Each call to `makeCounter()` produces an independent counter with its own `count` variable that is invisible to outside code. Calling the returned function increments and returns the private `count` without exposing it directly. This is the classic closure-based private state pattern.
A common mistake is creating closures inside a `var`-based loop and expecting each closure to capture a different loop index. Because `var` is function-scoped, all closures share the same single `i` variable, so they all see the final value after the loop ends. The fix is to use `let` (which is block-scoped, creating a new binding per iteration) or to use an IIFE to create a new scope for each iteration. This is one of the most frequent closure-related bugs in JavaScript.
Prefer `let` and `const` over `var` to avoid shared-variable surprises in loops and async code. Be mindful of memory: a closure that captures a large object or DOM node keeps it alive for the closure's entire lifetime, which can cause memory leaks if closures accumulate (e.g., inside event listeners that are never removed). Always explicitly remove event listeners or nullify references when they are no longer needed. Keeping closures small and purposeful makes code easier to reason about and profile.
© RM Full Stack & AI Engineer · All guides · Roadmaps · Open the app