Closures in JavaScript are created when a function remembers the variables from the scope where it was defined, even after that outer scope has finished execution. This behavior is one of the most important parts of JavaScript because it powers callbacks, data privacy patterns, function factories, and many real world framework features.
At first, closures can feel unusual because it seems like an inner function is carrying part of its surrounding environment with it. That is exactly what happens. The function keeps access to the variables it needs from its lexical scope, so those values remain available later when the function runs.
What a closure really is
Technically, a closure is the combination of a function and the lexical environment in which that function was created. In simpler terms, the function closes over the variables around it. Those variables are not copied like plain text. The function keeps access to them through the scope chain.
function outer() {
const message = "Hello from closure";
function inner() {
console.log(message);
}
return inner;
}
const savedFunction = outer();
savedFunction();
When `outer()` finishes, you might expect `message` to disappear completely. But the returned `inner` function still needs it, so JavaScript keeps that lexical environment available. That is why calling `savedFunction()` still prints the message.
Why closures happen
Closures happen because JavaScript uses lexical scope. A function can access variables from the place where it was defined. If that function is later returned, passed as an argument, or stored for later execution, it still carries access to the same surrounding variables. This is not a special trick added on top of JavaScript. It is a natural result of how scope works.
Closures become especially visible when a function outlives the block or function that created it. That is when developers notice that inner code still has access to outer data even though the original creator has already returned.
A simple counter example
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
Each time the returned function runs, it can still see and update `count`. The variable is private to that closure. Code outside the function cannot change it directly, which makes closures useful when you want to protect internal state from accidental modification.
Closures for data privacy
Before JavaScript had class fields and module systems with clearer boundaries, closures were one of the main ways to create private data. Even today, they remain a simple and effective technique when you want a value to stay available only through controlled functions.
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(500);
console.log(account.deposit(200));
console.log(account.getBalance());
The `balance` variable is not global and is not directly exposed as an object property. It can only be reached through the methods that close over it. That is a practical example of encapsulation using closures.
Function factories and customization
Closures are also useful when one function should generate specialized versions of another function. This pattern is often called a factory function. The outer function receives configuration data once, and the inner function reuses that data every time it runs.
function multiplyBy(multiplier) {
return function (value) {
return value * multiplier;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(10));
console.log(triple(10));
Here, `double` and `triple` are both closures, but they close over different values of `multiplier`. This is a clean way to create reusable, specialized behavior without repeating the same logic in several places.
Closures in callbacks and event handlers
JavaScript uses callbacks heavily, so closures appear naturally in asynchronous code and UI programming. A callback often needs access to values from the outer function that created it. Because of closures, that callback can still use those values later when the event or asynchronous result finally happens.
function greetUser(name) {
setTimeout(function () {
console.log("Welcome, " + name);
}, 1000);
}
greetUser("Shreya");
Even though the callback runs later, it still remembers `name`. This is one reason closures are central to timers, promises, event listeners, and many framework level abstractions.
Closures inside loops
One classic source of confusion appears when closures are created inside loops. With `var`, every closure may end up sharing the same loop variable because `var` is function scoped. With `let`, each loop iteration gets its own block scoped binding, which usually matches what developers expect.
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 500);
}
This prints `1`, `2`, and `3` because `let` creates a fresh binding for each iteration. If the same code used `var`, the delayed callbacks would all see the final loop value instead. This example shows how closures and scope rules are tightly connected.
Benefits and tradeoffs
| Benefit | Why it matters |
|---|---|
| Private state | Keeps internal data hidden from direct external access |
| Reusable factories | Lets one function create specialized functions |
| Callback support | Allows delayed code to remember outer values |
| Cleaner APIs | Exposes only the operations that should remain public |
Closures are powerful, but they should still be used deliberately. Deeply nested closures can make code harder to follow. Keeping unnecessary references alive can also hold memory longer than needed. In most cases this is manageable, but it is good practice to keep closure based designs simple and purposeful.
Common mistakes with closures
- Forgetting that inner functions keep access to outer variables, not frozen copies in every scenario.
- Creating unnecessary nesting that makes the data flow hard to read.
- Using `var` in loops and then being surprised by delayed callback behavior.
- Holding large objects in closures longer than necessary.
If you understand lexical scope, inner functions, and variable lifetime, closures stop feeling mysterious. They become a practical tool for structuring behavior and protecting state in a way that fits JavaScript naturally.
FAQ
Are closures a feature only for advanced developers?
No. Closures appear in ordinary JavaScript code all the time, especially with callbacks, event handlers, and factory functions.
Do closures copy outer variables?
Closures keep access to the variables from the lexical environment. The behavior depends on the variable and execution flow, but the key point is that the function retains access to outer scope.
Why do closures matter in real projects?
They help create private state, reusable function generators, and callbacks that remember the values they need when running later.
Closures for one time execution
One useful closure pattern is a function that should run successfully only once. The inner function remembers whether it has already executed and changes behavior on later calls. This pattern is common in initialization logic, feature setup, and guarding expensive work that should not repeat.
function once(fn) {
let executed = false;
return function (...args) {
if (executed) {
return "Already executed";
}
executed = true;
return fn(...args);
};
}
const startServer = once(() => "Server started");
console.log(startServer());
console.log(startServer());
The `executed` variable stays alive inside the returned function. Without a closure, the function would not remember whether it had run before. This is a strong example of state that belongs to behavior instead of being stored as a loose global variable.
Closures for memoization
Closures also help with memoization, which means caching a computed result so repeated calls can return faster. The outer function creates a cache object once, and the inner function reuses that cache whenever it receives the same input again.
function createSquareCache() {
const cache = {};
return function (value) {
if (cache[value] !== undefined) {
return cache[value];
}
cache[value] = value * value;
return cache[value];
};
}
const square = createSquareCache();
console.log(square(12));
console.log(square(12));
This is a practical use case because the cache remains private, yet it survives between calls. The caller gets the speed benefit without needing to manage the internal storage directly.
Closures and readability
Closures are powerful, but not every problem should be solved with deep layers of nested functions. When too many inner functions depend on too many outer values, reading the code becomes harder. The best closure based code keeps the number of captured variables small and makes the ownership of state obvious.
If a closure keeps large objects alive longer than necessary, memory can also remain occupied longer than intended. This does not mean closures are bad. It means they should be used intentionally, just like any other useful language feature. Clean naming, small functions, and limited nesting keep closure heavy code maintainable.
Closures vs global state
Global variables are easy to reach, but they expose data to the whole program. Closures take the opposite approach by exposing behavior while hiding the data behind that behavior. In many situations, this leads to safer APIs because code outside the closure can use the allowed operations without directly mutating the internal values.
Reading captured state over time
The most important habit when reading closure based code is to track which variables are captured and when the inner function will execute. Some closures run immediately, while others run much later through timers, event listeners, or asynchronous callbacks. The captured variables may still be available, but their values may also have changed by the time the inner function actually runs.
That is why closures are not just about syntax. They are about time as well as scope. You are often working with a function that remembers a surrounding environment and then uses that environment later. Once you start thinking in terms of captured state and execution timing, many callback behaviors in JavaScript become much easier to predict.
Used well, closures give JavaScript much of its expressive power. They let code stay compact while still carrying private state, configuration, and delayed behavior. Used carelessly, they can hide too much context. The right balance is to keep the captured data small, intentional, and easy to identify from the surrounding code.