What you'll learn
Quick Answer
A closure is a function that remembers the variables from the scope where it was created, even after that outer function has finished running. In JavaScript, every function you define forms a closure over the variables around it. This is what lets you build counters, keep data private, and write callbacks that hold on to the values they need.
What Are JavaScript Closures?
A closure is one of those ideas that sounds scary until you see it in action. In plain words: a closure is a function that remembers the variables from the place where it was created, even after that place has finished running.
Here is the one-line definition to keep in your head: whenever you create a function inside another function, the inner function keeps a live link to the outer function's variables. That link is the closure.
You have probably used JavaScript closures already without naming them, because every callback, every event handler, and every module quietly relies on them. Let's build the intuition step by step, with examples you can paste straight into your browser console and run.
Lexical Scope: The Foundation
To understand closures, you first need lexical scope. "Lexical" just means "based on where you wrote the code." In JavaScript, an inner function can see the variables of the function that surrounds it, but not the other way around.
function outer() {
const message = "Hello from outer";
function inner() {
console.log(message); // inner can read message
}
inner();
}
outer(); // logs: Hello from outerHere inner reaches "outward" to find message. Nothing surprising yet. The real magic starts when we return inner and call it later, after outer has already finished. Normally you would expect message to be gone, but with a closure it stays alive.
Example 1: A Counter That Remembers
The counter is the classic closure example. We want a function that gives us 1, then 2, then 3, remembering its own count between calls.
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const next = makeCounter();
console.log(next()); // 1
console.log(next()); // 2
console.log(next()); // 3Even though makeCounter() has already finished running, the returned function still holds a live reference to count. Each call updates the same count. That private, persistent variable is the closure at work.
Call makeCounter() again and you get a brand-new, independent counter with its own count starting at 0. The two counters never interfere with each other.
Example 2: Private Variables Without Classes
Closures also let you hide data. Variables inside the outer function are invisible from outside, and the only way in is through the functions you choose to return. This is how you get true privacy in JavaScript without classes or the # private-field syntax.
function createBankAccount(startingBalance) {
let balance = startingBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) {
return "Not enough balance";
}
balance -= amount;
return balance;
},
getBalance() {
return balance;
},
};
}
const account = createBankAccount(100);
account.deposit(50); // 150
account.withdraw(30); // 120
console.log(account.getBalance()); // 120
console.log(account.balance); // undefined - no direct accessThere is no way to set balance to a negative number from outside, or even read it directly. The balance variable is sealed inside the closure, and only the three methods you exposed can touch it. This "controlled access" pattern shows up all over real-world code.
The Classic Loop Gotcha (var vs let)
Now for the trap that catches almost every beginner. Look at this loop that stores three functions, each meant to return its own index.
var callbacks = [];
for (var i = 0; i < 3; i++) {
callbacks.push(function () {
return i;
});
}
console.log(callbacks[0]()); // 3
console.log(callbacks[1]()); // 3
console.log(callbacks[2]()); // 3Surprised? You probably expected 0, 1, 2. But with var, there is only one i shared by the whole loop. All three functions close over that same i, and by the time you call them the loop has finished, leaving i at 3.
The fix is a one-word change: use let instead of var.
const callbacks = [];
for (let i = 0; i < 3; i++) {
callbacks.push(function () {
return i;
});
}
console.log(callbacks[0]()); // 0
console.log(callbacks[1]()); // 1
console.log(callbacks[2]()); // 2let is block-scoped, so each loop iteration gets a fresh i. Each function closes over its own copy, and you get 0, 1, 2. Before let existed, developers solved this with an IIFE (immediately invoked function) to create a new scope per iteration, but today the simple answer is just use let.
Why Closures Power Callbacks and Modules
Closures are not just a party trick. They are the engine behind two patterns you will use every single day.
Callbacks remember their data
When you pass a function to setTimeout, an event listener, or fetch, it runs later. Closures are what let it still see the variables it needs.
function greetLater(name) {
setTimeout(function () {
console.log("Hello, " + name);
}, 1000);
}
greetLater("Aarav"); // after 1 second: Hello, AaravBy the time the timer fires, greetLater has long returned, but the callback still remembers name through its closure.
The module pattern
Wrap code in a function, keep some variables private, and return only what you want to expose. That is a module.
const cart = (function () {
let items = [];
return {
add(item) {
items.push(item);
},
total() {
return items.reduce((sum, item) => sum + item.price, 0);
},
};
})();
cart.add({ name: "Notebook", price: 40 });
cart.add({ name: "Pen", price: 10 });
console.log(cart.total()); // 50The items array is completely private; the outside world can only add to the cart and read its total. Want to go deeper into patterns like this? Our free JavaScript course walks through scope, closures, and modules with hands-on exercises.
Common Mistakes to Avoid
- Thinking closures copy values. A closure captures the variable, not a snapshot. If the variable changes later, the closure sees the new value, which is exactly what the
varloop bug demonstrates. - Creating closures in hot loops. Every closure holds some memory. Making thousands inside a tight loop can add up. It is rarely a problem for beginners, but it is worth knowing.
- Forgetting closures keep memory alive. As long as a closure exists, the variables it references cannot be garbage-collected. Hold onto a large object in a closure and it stays in memory.
- Reaching for
var. Almost every closure surprise in loops disappears when you switch toletandconst. Make them your default.
Key Takeaways
Closures feel like magic at first, but they follow one simple rule: an inner function keeps access to the variables of the scope where it was born.
- Closures are built on lexical scope - where you write a function decides what it can see.
- They let you build counters and other state that persists between calls.
- They give you private variables without needing classes.
- The
varloop bug is fixed by switching tolet. - Callbacks and the module pattern both run on closures.
The best way to make this stick is to type each example yourself and change the numbers. Once you have written a few closures by hand, they stop being scary and become one of your favourite tools.
Frequently Asked Questions
What is a closure in JavaScript in simple terms?
A closure is a function that remembers the variables from the scope where it was created, even after that outer scope has finished running. In practice, any time you define a function inside another function, the inner one keeps a live link to the outer function's variables, and that link is the closure.
Why does the var loop print the same number every time?
Because var is function-scoped, so the whole loop shares a single i. Every function you create in the loop closes over that same i, and by the time they run the loop has finished, leaving i at its final value. Switching to let gives each iteration its own fresh i, so each function captures the correct value.
Are closures and lexical scope the same thing?
No, but they are closely related. Lexical scope is the rule that a function can see the variables of wherever it was written. A closure is what happens when a function actually uses that access after its outer function has returned, keeping those variables alive. Lexical scope is the foundation; the closure is the result.
Do closures cause memory leaks?
Not on their own. A closure keeps its referenced variables in memory only for as long as the closure itself is reachable. A leak happens when you accidentally keep a closure alive longer than needed, for example an event listener you never remove that holds a large object. Clean up listeners and avoid capturing big data you do not need, and closures are safe.
When should I actually use closures?
Whenever you need a function to remember state between calls (like a counter), to keep data private (like a bank balance or a shopping cart), or to pass a callback that must hold onto some values (like a timer or event handler). You will also rely on them every time you build a module. In modern JavaScript you use closures constantly, often without thinking about the name.
