What is a Closure in JavaScript
A closure in JavaScript is a function that remembers and continues to have access to the variables from the place where it was originally created, even after that outer function has already finished running. In simple words, a closure is a function bundled together with its lexical environment — that is, the set of variables that were in scope at the moment the function was defined.
This means that even if the outer function returns and its execution context is destroyed, the inner function that was created inside it does not lose connection with those variables. It still carries a permanent reference to them. This link between a function and its surrounding variables is not a copy — it is a live reference, which is why any changes to those variables are still visible through the closure.
Closures are not a special syntax or a keyword. They are simply a natural outcome of how JavaScript handles functions and scopes. Every single function in JavaScript automatically forms a closure with the environment it was created in. You are already using closures whenever you write nested functions — you just may not have realised it yet. Understanding this concept clearly will change how you read and write JavaScript forever.
Why Closures Exist in JavaScript
Closures exist because JavaScript functions are first-class citizens. You can pass a function as an argument, return it from another function, assign it to a variable, and store it inside objects or arrays. To make this work safely, the language must guarantee that when a function is moved around, it still has access to the variables it was originally designed to work with. That guarantee is what closures provide.
If closures did not exist, then passing a function from one place to another would break it — the function would lose its variables the moment its parent finished executing. By giving every function a permanent link to its lexical environment, JavaScript allows functions to travel anywhere in the program while still behaving correctly. This is what enables callbacks, event handlers, asynchronous code, and functional programming patterns to work so elegantly.
Closures also power some of the most important features of modern JavaScript, including data privacy, function currying, memoization, the module design pattern, and the once pattern used to ensure a function runs only one time. Without closures, none of these techniques would be possible. This is why closures are often called the backbone of real-world JavaScript.
Internal Working: How Closures Work Under the Hood
To understand how closures actually work, you need to understand the concept of the Lexical Environment. Every time a function is created in JavaScript, it is attached to the lexical environment of its surrounding code. The lexical environment is essentially a reference to the memory space of the parent scope. This attachment stays with the function forever, even after the parent finishes executing.
When a function is invoked, the JavaScript engine creates a new Execution Context for it. This Execution Context has its own local memory, but it also holds a hidden reference to the lexical environment of its parent. When the function tries to access a variable, the engine first looks in the local memory. If it does not find the variable there, it follows the lexical environment reference to the parent scope, and keeps walking up this chain of parents until it either finds the variable or reaches the global scope. This chain of connected environments is called the Scope Chain.
The magic of closures happens when a function is returned from another function. Normally, when a function finishes, its Execution Context is destroyed and its local variables would be cleaned up by garbage collection. However, if an inner function is returned and still alive somewhere in the program, that inner function still holds a reference to the parent's lexical environment. Because of that reference, the parent's variables are kept in memory and are never garbage-collected. The returned function can continue to read and even modify those variables for as long as it exists.
Inner Function
Lexical Environment
Practical Examples of Closures
Let us now walk through a series of clear, progressive examples so you can build a rock-solid intuition for closures. We will start with the simplest case and gradually move into the real-world patterns.
Example 1: The Most Basic Closure
function x() {
var a = 7;
function y() {
console.log(a);
}
y();
}
x();
// Output: 7
Here, the inner function y prints the variable a, which is defined in the outer function x. Even though a is not declared inside y itself, the inner function can reach outside into the lexical environment of its parent and find a there. This is the simplest possible closure in action — an inner function accessing a variable from its outer function.
Example 2: Returning the Inner Function (The Real Closure)
function x() {
var a = 7;
function y() {
console.log(a);
}
return y;
}
var z = x();
console.log(z); // Output: Æ’ y() { console.log(a); }
z(); // Output: 7
This is the heart of closures. We are calling x() which returns the inner function y. By the time we call z() a few lines later, the outer function x has already completed and its Execution Context has been destroyed. Yet, when we invoke z(), it still prints 7. How? Because the returned function y did not lose its link to the lexical environment of x. It carries that environment along with itself — that bundle is the closure.
Example 3: Closure Remembers the Reference, Not the Value
function x() {
var a = 7;
function y() {
console.log(a);
}
a = 100; // value updated AFTER y is defined
return y;
}
var z = x();
z(); // Output: 100
This example demonstrates a subtle but critical truth about closures — they hold a reference to the variable, not a snapshot of its value. Even though y was defined when a was 7, by the time we return y, the value of a has changed to 100. When we eventually call the closure, it reads the latest value from the shared memory — hence the output is 100, not 7.
Example 4: Nested Closures and the Scope Chain
function outer() {
var a = 10;
function middle() {
var b = 20;
function inner() {
console.log(a + b);
}
return inner;
}
return middle();
}
var fn = outer();
fn(); // Output: 30
Here the innermost function inner can reach both b from middle and a from outer. This is the scope chain at work. The closure created by inner holds references to every lexical environment above it, all the way up to the global scope. This layered access is how JavaScript resolves variables that are not locally declared.
Output of Each Example
Here is the combined output summary for the examples we just studied. Notice how closures allow inner functions to keep working long after their parents have finished.
| Example | Output | Reason |
|---|---|---|
| Basic nested call | 7 | Inner function reads variable from outer scope |
| Returning inner function | Æ’ y() ... then 7 | Closure keeps outer scope alive after return |
| Updated value before return | 100 | Closure holds a reference, not a snapshot |
| Nested closures | 30 | Scope chain allows access to multiple outer scopes |
Step-by-Step Execution of a Closure
Let us carefully trace Example 2 so the full mechanism becomes crystal clear. Imagine we are the JavaScript engine and we are walking through every single step.
Common Mistakes Developers Make with Closures
Closures are elegant but they come with a few traps. Knowing them in advance will save you a lot of debugging time in real projects.
Mistake 1: Thinking Closures Store a Snapshot of the Value
Many beginners assume that when a function "captures" a variable, it captures the current value. This is wrong. A closure captures a reference to the variable. If the variable changes later, the closure sees the new value.
function createLoggers() {
var value = 1;
var logNow = function () { console.log(value); };
value = 99;
return logNow;
}
var log = createLoggers();
log(); // Output: 99 (not 1)
Mistake 2: Classic var Loop Trap
One of the most famous closure traps involves using var inside a loop with setTimeout. Because var is function-scoped, all callbacks share the same i and end up printing the final value.
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// Output after 1 second:
// 4
// 4
// 4
The fix is to use let, which is block-scoped. Each iteration creates a fresh i, and each closure captures its own copy.
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// Output after 1 second:
// 1
// 2
// 3
Mistake 3: Creating Memory Leaks by Accident
Because closures keep their entire lexical environment alive, they can accidentally prevent large objects from being garbage collected. If a closure captures a huge array just to read a small value from it, the whole array stays in memory as long as the closure exists.
Mistake 4: Not Realizing Every Function is a Closure
Developers often think closures are a rare or advanced feature. In reality, every single function in JavaScript creates a closure with its surrounding environment. Once you accept this, many language behaviors — like callbacks, event handlers, and async code — become much easier to understand.
Comparison: Closures Across Different Languages
Closures are not unique to JavaScript, but JavaScript implements them in a particularly elegant and accessible way. Here is a quick comparison with other popular languages.
| Language | Supports Closures? | Notes |
|---|---|---|
| JavaScript | Yes — first-class | Every function creates a closure automatically |
| Python | Yes | Inner functions capture outer scope; requires nonlocal for writes |
| Java | Limited (lambdas) | Variables must be effectively final |
| C | No | No support for nested functions or closures |
| C++ | Yes (lambdas) | Explicit capture syntax required |
| Swift / Kotlin / Rust | Yes | First-class support with explicit capture semantics |
Of all these, JavaScript is arguably the most closure-friendly language because you do not need any special syntax, keywords, or annotations to create one. Closures happen automatically and seamlessly every time you nest a function.
Real-World Usage of Closures
Closures are not just theory — they power some of the most important and elegant patterns in real-world JavaScript applications.
Scenario 1: Data Privacy (Private Variables)
Closures are the classic way to create private state in JavaScript. Variables defined inside the outer function cannot be accessed from outside, but the inner functions returned from it can use them freely.
function createCounter() {
var count = 0;
return {
increment: function () { count++; },
decrement: function () { count--; },
getValue: function () { return count; }
};
}
var counter = createCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.getValue()); // Output: 2
console.log(counter.count); // Output: undefined (private!)
The count variable is completely hidden. Outside code cannot read, write, or corrupt it. Only the three methods returned from createCounter can interact with it — this is genuine data privacy built with nothing but closures.
Scenario 2: Function Currying
Currying is a functional programming technique where a function takes arguments one at a time, returning a new function for each additional argument. Closures make currying possible and elegant.
function multiply(a) {
return function (b) {
return function (c) {
return a * b * c;
};
};
}
console.log(multiply(2)(3)(4)); // Output: 24
var doubleAndTriple = multiply(2)(3);
console.log(doubleAndTriple(5)); // Output: 30
console.log(doubleAndTriple(10)); // Output: 60
Scenario 3: The Once Pattern
Sometimes you want a function to run only once, no matter how many times it is called. Closures make this extremely clean.
function once(fn) {
var called = false;
return function () {
if (!called) {
called = true;
return fn.apply(this, arguments);
}
return "Already executed";
};
}
var initApp = once(function () {
console.log("App initialised");
});
initApp(); // Output: App initialised
initApp(); // Output: Already executed
initApp(); // Output: Already executed
Scenario 4: Memoization (Caching Results)
Closures are perfect for storing cached results of expensive computations, so repeated calls with the same input return instantly.
function memoize(fn) {
var cache = {};
return function (n) {
if (cache[n] !== undefined) {
console.log("From cache");
return cache[n];
}
console.log("Computing...");
var result = fn(n);
cache[n] = result;
return result;
};
}
var slowSquare = memoize(function (x) {
return x * x;
});
console.log(slowSquare(5)); // Computing... 25
console.log(slowSquare(5)); // From cache 25
console.log(slowSquare(6)); // Computing... 36
console.log(slowSquare(5)); // From cache 25
Scenario 5: The Module Design Pattern
Before ES6 modules, closures were the only clean way to create reusable modules with public APIs and private internals. This pattern is still widely used today.
var UserModule = (function () {
var users = [];
function addUser(name) {
users.push(name);
}
function getUsers() {
return users.slice();
}
return {
addUser: addUser,
getUsers: getUsers
};
})();
UserModule.addUser("Rahul");
UserModule.addUser("Priya");
console.log(UserModule.getUsers()); // Output: ["Rahul", "Priya"]
console.log(UserModule.users); // Output: undefined (private!)
Summary
A closure is simply a function bundled together with its lexical environment. This means the function permanently remembers the variables that were in scope at the moment it was created, and it can continue to access them even after the outer function has finished executing and its Execution Context has been destroyed.
Closures exist in JavaScript because functions are first-class citizens. They can be returned, passed around, and stored anywhere in the program. To keep them working correctly wherever they go, the language attaches each function to a live reference of its surrounding scope. This attachment, combined with the scope chain, is what enables closures to function naturally without any special syntax.
We saw closures in action through progressive examples — from the simplest nested function, to returned functions, to closures that hold references rather than snapshots, and finally to deeply nested closures accessing multiple outer scopes. We then applied closures to real-world patterns that every JavaScript developer should know: data privacy with private variables, function currying, the once pattern, memoization for caching, and the classic module design pattern.
We also covered the common mistakes developers make — assuming closures capture values, falling into the var loop trap, creating accidental memory leaks, and not realising that every function is inherently a closure. When you understand closures deeply, these traps become easy to avoid, and you unlock a new level of confidence in your JavaScript code. Closures are truly the soul of JavaScript, and mastering them is one of the most rewarding milestones in your journey as a developer.
| Concept | Key Takeaway |
|---|---|
| Closure | Function + its lexical environment bundled together |
| Lexical Environment | The scope where the function was originally defined |
| Scope Chain | Linked list of environments used to resolve variables |
| Reference, Not Snapshot | Closures read the current value from memory, not the old one |
| Data Privacy | Hide state in outer function, expose only via returned API |
| Currying | Break a multi-argument function into a chain of single-argument ones |
| Once / Memoize / Module | Classic real-world patterns built entirely on closures |
| Common Trap | Using var inside a loop with setTimeout — fix with let |