Closures in JavaScript
Understand how a function keeps access to its variables, even after the function that created them has finished.
- 01Variables that stayCall a function after the function that made it has ended.
- 02Shared or separate?Change one line and see whether two counters share one count.
- 03A private countTry to change a cart’s hidden item count from outside.
What is a closure?
A closure is a function that keeps using the variables from the place where it was written. Here is what that means:
- An outer function runs and makes some variables.
- Inside it, an inner function is made. The inner function uses those variables.
- The outer function ends. The inner function can still read and change those variables later.
We'll start with a function that remembers a short message. Then we'll use a counter to see that a remembered variable can change, too.
A function remembers the variables from where it was written, not from where it is called.
You pack a backpack at home and take it with you. Later, far from home, you can still open it and use what is inside.
- In real life: You pack a backpack at home
- In JavaScript: The outer function makes its variables
- In real life: You take the backpack when you leave
- In JavaScript: The inner function keeps those variables
- In real life: You open it later, somewhere else
- In JavaScript: The inner function reads them when it is called later
- In real life: A new trip gets a new backpack
- In JavaScript: Each new call of the outer function makes new variables
You don't write anything special to make a closure. Every JavaScript function keeps a link to the variables around the place where it was written. Returning a function from another function just makes this easy to see.
You can run the examples below one step at a time. Click the blue line, or press Step, to run the next step. Each step says what happened: a jump into a function, a variable change, or a return. Press Back to go one step back and compare.
Start with a nested function
A nested function is a function written inside another function. The inner function can read the variables of the outer function. This rule is called lexical scope. "Lexical" means "about where the code is written". So a function can use the variables around the place where it is written.
Here is a small example. makeGreeter takes a name and returns a new function. The new function makes a greeting from that name.
function makeGreeter(name) {
return () => `Hello, ${name}!`;
}
const greetAsha = makeGreeter("Asha");
console.log(greetAsha());Line by line:
- Line 1 starts
makeGreeter. Its parameter,name, is a variable that belongs to this call. - Line 2 returns a new arrow function. The arrow function uses
name, but it does not run yet. - Line 5 calls
makeGreeter("Asha"), sonameis"Asha". The call returns the arrow function, and we save it asgreetAsha. NowmakeGreeterhas ended. - Line 6 calls
greetAsha(). It still readsname, so it printsHello, Asha!.
makeGreeter("Asha") made the box name and then ended. The function it returned, greetAsha, still reads that box. So greetAsha() prints Hello, Asha!.The step-through below uses a very similar function, createGreeting. It takes a message and returns a function called greet, which gives the message back. You can change the message on line 7. Step into the first call and watch it return. Then follow the second call back to the message.
Click the blue line, or press Step. Start at the call on line 7. A function's body runs only when the function is called.
script
function createGreeting(message) { return function greet() { return message; };} greet();When you change the text, the walkthrough starts again. Press Step or click the blue line to follow your new message.
Keep these two moments apart:
- Line 7 makes the function.
createGreeting("Hello!")runs and returns a function. We save that function asgreet. The call tocreateGreetinghas now ended. - Line 8 calls the function.
greet()runs the inner function. It still readsmessagefrom the call that has already ended.
That kept access to message is the closure. message is not a global variable (a variable that all code can see). And we did not pass it to greet().
See a closure in action
INTERACTIVEThe greeting only reads a variable. A counter goes one step further. It changes a variable, and the next call sees the new value.
makeCounter makes a variable, count, that starts at 0. It returns a function that adds 1 to count and returns the new number. Press Step to follow each call into the function. Watch the value of count appear beside line 2. When the walkthrough ends, use the buttons to call a() or b() again.
Step into each call. Watch count appear beside the code, stay after makeCounter() ends, and change.
script
function makeCounter() { let count = 0; return function increment() { count += 1; return count; };} a();a(); const b = makeCounter();b();The second a() did not run makeCounter() again. It ran the returned function. That function changed the same count from 1 to 2.
Each call makes new variables
A function that makes and returns other functions is often called a factory. makeCounter is a factory. Each call to makeCounter() makes a new count, starting at 0.
The code below makes two counters, first and second.
function makeCounter() {
let count = 0;
return () => ++count;
}
const first = makeCounter();
const second = makeCounter();
console.log(first(), second(), first());Line by line:
- Lines 1 to 4 are the same
makeCounteras before, written shorter.++countadds 1 tocountand gives back the new value. - Line 6 calls
makeCounter(). This call makes onecount, andfirstuses it. - Line 7 calls
makeCounter()again. This call makes a secondcount, andseconduses it. - Line 8 calls
first(), thensecond(), thenfirst()again. It prints1 1 2.second()gives 1 because itscountis new.
makeCounter() call makes a new count box. first and second never share. After line 8, first's box holds 2 and second's box holds 1.But const b = a is different. It does not call makeCounter(). It gives the same function a second name. Two names for one function means one count.
In the step-through below, you choose what line 10 says. Pick one option. Then follow a(), b(), and a() again. With const b = a, notice that JavaScript never goes into makeCounter() a second time.
Step into each call. Watch count appear beside the code, stay after makeCounter() ends, and change.
script
function makeCounter() { let count = 0; return function increment() { count += 1; return count; };} const b = makeCounter();a === b;a();b();a();The rule to remember: a new call to the factory makes new variables. A second name for the same function shares the old ones.
A practical use: a private count
Imagine a shopping cart. Its item count should change only through an add method. Nothing else should be able to set it. A closure can do this. The count lives inside a function, so it is private: code outside cannot see it or change it directly.
createCart makes a variable, items. It returns an object with two methods (functions stored on an object): add and getCount. Step through the code. First watch items being made. Then watch add() change it. At line 16, use Back and Step to compare the object's new items property with the private items variable.
Click the blue line to follow the cart from the first line to the last. Each step explains one thing that happens.
script
function createCart() { let items = 0; return { add() { items += 1; return items; }, getCount() { return items; } };} cart.add();cart.items = 99;cart.getCount();add and getCount were made in the same call to createCart. So they share one items variable. Neither method has its own copy.
add() and getCount() were made in the same createCart() call, so both use the one items box. Code outside has no name for that box. Only the two methods can reach it.Line 16, cart.items = 99, adds a new property called items to the object. It does not touch the private variable, so getCount() still returns 1. Calling createCart() again would make a new cart with its own items.
function once(action) {
let used = false;
return () => {
if (used) return "Already paid";
used = true;
return action();
};
}
const payOnce = once(() => "Payment sent");
console.log(payOnce(), payOnce());Here is another private variable. once takes an action and returns a new function. Its variable, used, starts as false.
- The first call sees that
usedisfalse. It setsusedtotrueand runs the action, which returns"Payment sent". - The second call sees that
usedistrue. It returns"Already paid"and does not run the action.
So line 10 prints Payment sent Already paid.
function makeProductHandler(productId) {
return () => `Opening product ${productId}`;
}
const openNotebook = makeProductHandler("notebook-7");
console.log(openNotebook());A click handler is a function that runs when someone clicks a button. On line 5, makeProductHandler("notebook-7") returns a handler that remembers the product ID. A real button could call openNotebook later. Here, line 6 calls it right away and prints Opening product notebook-7. No global variable is needed to hold the product ID.
There are three common places to keep a value that changes. Read each row as: "If I keep it here, this is where it lives, and this is who can change it."
| Where you keep it | Where the value lives | Who can change it |
|---|---|---|
| In a closure | In a variable inside the function call that made it. | Only the functions made in that same call. |
| In a global variable | In a variable at the top level of the code. | Any code that knows the variable's name. |
| In an object property | In a named property on an object. | Any code that has the object. |
You'll also see closures in these places:
- Click handlers that remember a setting, like the product ID above.
- Debounce helpers. A debounce helper waits until you stop typing before it runs. It keeps its timer in a closure.
- Callbacks that use variables from the code around them.
Common misconceptions
A closure does not freeze a copy of each value. It keeps the variable itself. But a value that was already built from a variable does not update later. The step-through below shows both:
live()readscounteach time it runs, so it sees the newest number.saved()returnsmessage. That string was built once, whencountwas 0.
Watch the two variables beside lines 2 and 3. Step through the methods to see why only one result changes.
script
function createReaders() { let count = 0; const message = `Count is ${count}`; return { increment() { count += 1; return count; }, live() { return `Count is ${count}`; }, saved() { return message; } };} readers.increment();readers.live();readers.saved();let status = "saving";
const report = () => console.log(status);
status = "saved";
setTimeout(report, 0);setTimeout(report, 0) asks JavaScript to run report later, after the rest of the code. Line by line:
- Line 1 sets
statusto"saving". - Line 2 makes
report, a function that printsstatus. - Line 3 changes
statusto"saved". - Line 4 runs
reportlater. By thenstatusis"saved", so it printssaved.
report keeps the variable status, not the word "saving".
function makeReaders() {
let name = "Asha";
return {
read: () => name,
rename: (nextName) => { name = nextName; },
};
}
const readers = makeReaders();
const readNow = readers.read;
readers.rename("Mina");
console.log(readNow(), readers.read());makeReaders makes one variable, name, set to "Asha". It returns two functions made in the same call: read and rename.
- Line 10 saves
readers.readasreadNow. It is the same function with a second name. - Line 11 calls
rename("Mina"). This changes the one sharedname. - Line 12 calls both readers. Both read the same
name, so it printsMina Mina.
for (var i = 0; i < 3; i += 1) {
setTimeout(() => console.log("var", i), 0);
}
for (let j = 0; j < 3; j += 1) {
setTimeout(() => console.log("let", j), 0);
}Each loop sets up three timers. The timers run after both loops have ended.
- The
varloop has oneifor the whole loop. All three callbacks read that onei, which is 3 by then. They printvar 3three times. - The
letloop makes a newjfor each pass. Each callback reads its ownj. They printlet 0,let 1, andlet 2.
var, all three read the one i, which is 3 by then. With let, each pass of the loop gets a new j, so each callback reads its own number.“A closure saves a copy of a value.”
No. It keeps the variable itself. In the step-through above, count changes, and live() sees the change. message stays the same because it is a string that was built once. Reading it again does not build it again.
“Every function gets its own separate variables.”
Not always. Functions made in the same outer call share its variables, like the two cart methods. Only separate calls to the factory make separate variables.
“You must return a function to make a closure.”
No. Returning a function is one way to use it later. Passing an inner function to a timer or a click listener works too. That function keeps its outer variables as well.
“A closure keeps its variables forever.”
No. When no code can reach the function any more, JavaScript can free its variables. But a callback that lives a long time can keep big objects in memory. So remove listeners and clear timers you no longer need.
JavaScript frees memory that no code can reach any more. This is called garbage collection. A closure's variables stay while some function, listener, or timer can still use them. Once the last one is removed, JavaScript may free them. JavaScript decides when, so your code should not depend on the exact moment.
A var loop has one loop variable, and every callback shares it. A let loop makes a new loop variable for each pass. That is why delayed callbacks can print different results with var and with let.
Practice exercises
4 EXERCISESThe exercises go from reading a counter to building a small helper. Try each one before you open the hints and the worked solution.
Read the counter and predict both printed lines. makeCounter is called only once.
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());The returned function keeps count. The first call changes count from 0 to 1. The second call changes the same count to 2.
Write the small function shown in the starter code. Then check the greeting it gives for Asha.
function makeGreeter(name) {
return function () {
return "Hello, " + name + "!";
};
}
const greet = makeGreeter("Asha");
console.log(greet());function makeGreeter(name) {
return function () {
return "Hello, " + name + "!";
};
}
const greet = makeGreeter("Asha");
console.log(greet());makeGreeter keeps name in its call. The returned function reads name later, so greet() returns Hello, Asha!
First predict what the three callbacks return. Then find the one word that makes them all return the same number, and change it.
const callbacks = [];
for (var index = 0; index < 3; index += 1) {
callbacks.push(() => index);
}
console.log(callbacks[0]());
console.log(callbacks[1]());
console.log(callbacks[2]());const callbacks = [];
for (let index = 0; index < 3; index += 1) {
callbacks.push(() => index);
}
console.log(callbacks[0]());
console.log(callbacks[1]());
console.log(callbacks[2]());With var, all three callbacks share one index, which is 3 after the loop. So they return 3, 3, 3. Changing var to let makes a new index for each pass, so they return 0, 1, and 2.
Make a helper for a Pay button. It should run the action the first time only, and give a clear message on later calls.
function once(fn) {
let used = false;
return function () {
if (used) return "Already paid";
used = true;
return fn();
};
}
const pay = once(() => "Payment sent");
console.log(pay());
console.log(pay());function once(fn) {
let used = false;
return function () {
if (used) return "Already paid";
used = true;
return fn();
};
}
const pay = once(() => "Payment sent");
console.log(pay());
console.log(pay());The returned function keeps used. The first call sets used to true and runs fn, which returns Payment sent. The next call sees that used is true and returns Already paid without running fn.
Check your understanding
6 QUESTIONSPredict each output first. Then read why each choice is right or wrong.
const counter = makeCounter();
const alias = counter;
counter(); // ?
alias(); // ?
counter(); // ?Question 1 of 6What does this counter code print?
Read the code, then predictfunction makeCounter() { let count = 0; return () => ++count; } const counter = makeCounter(); const alias = counter; console.log(counter()); console.log(alias()); console.log(counter());Choose an answer to see the explanation.
Question 2 of 6Which sentence best describes a closure?
Choose an answer to see the explanation.
Question 3 of 6What does getCount() print at the end?
Read the code, then predictfunction createCart() { let items = 0; return { add() { items += 1; return items; }, getCount() { return items; } }; } const cart = createCart(); cart.add(); cart.items = 99; console.log(cart.getCount());Choose an answer to see the explanation.
Question 4 of 6What does the var loop print?
Read the code, then predictconst callbacks = []; for (var index = 0; index < 3; index += 1) { callbacks.push(() => index); } console.log(callbacks[0](), callbacks[1](), callbacks[2]());Choose an answer to see the explanation.
Question 5 of 6Why do the two methods from one createCart() call share
items?Choose an answer to see the explanation.
Question 6 of 6What do live() and saved() print?
Read the code, then predictfunction createReaders() { let count = 0; const message = "Count is " + count; return { increment() { count += 1; return count; }, live() { return "Count is " + count; }, saved() { return message; }, }; } const readers = createReaders(); readers.increment(); console.log(readers.live()); console.log(readers.saved());Choose an answer to see the explanation.
Key takeaways
- A closure is a function plus the variables from the place where it was written.
- The outer function can end, and the function it returned can still use its variables.
- A closure keeps the variable itself, not a frozen copy of its value.
- Each call to a factory makes new variables. Functions made in the same call share them.
- Closures give you private variables, run-once helpers, and click handlers that remember a setting.
Remember the idea, not the long words.
The outer function ends. Its variables can stay, as long as a function made inside it still uses them.