Closures
In the previous lesson a function was a recipe card. Now let's clip a note to the card — a variable the recipe works with. The card has been carried out of the kitchen where it was written, and the note stays with it.
As you already know, a function sees the variables of the place where it is written: this is lexical scope. So a function written inside another function sees its variables. But the inner function can be returned, and then the outer function finishes before the inner one is called:
function makeGreeter(greeting) {
return (name) => `${greeting}, ${name}`;
}
const hi = makeGreeter("Hi");
const hello = makeGreeter("Hello");
console.log(hi("Ann")); // Hi, Ann
console.log(hello("Bob")); // Hello, BobLet's go through it line by line:
makeGreeter("Hi")gets"Hi"in the parametergreeting, returns an arrow function and finishes.const hiholds that arrow function.makeGreeter("Hello")does the same with its owngreeting, and the result goes intohello.hi("Ann")printsHi, Ann, even though the callmakeGreeter("Hi")ended long ago. The arrow function still knows itsgreeting.
Closure
A closure is a function together with the variables of the place where it was created. The returned function keeps not a copy of the value but the variable greeting itself, the one from its own makeGreeter call. That variable lives as long as the function that uses it, even though the makeGreeter call itself has ended.
Each call of makeGreeter creates its own parameter greeting and its own arrow function. So hi and hello have different greetings and do not interfere with each other: two cards, each with its own note.
Interviewers like to ask this: "What is a closure?" A good answer has two parts:
- the function remembers the variables of the place where it was created;
- those variables live on after the outer function has finished.
A counter
A closure can not only read a variable but also change it. The variable then keeps state between calls. A function that creates such closures is called a factory:
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const next = makeCounter();
console.log(next()); // 1
console.log(next()); // 2
console.log(next()); // 3The line let count = 0 runs once, when makeCounter is called. The calls of next do not repeat it: they increase the same count, so the count grows. A new call of makeCounter would create a new variable count, and the new function's count would start from 1 again.
Here the note picture needs a caveat. A paper note is a copy: if you later fix the original, the note stays as it was. A closure holds not a copy but the variable itself, so the next call sees every change to count.
Press "Next step" and watch the captured row: the frame of the makeCounter call disappears, while count stays with the closure and grows.
1function makeCounter() {2 let count = 0;3 return () => {4 count += 1;5 return count;6 };7}8 9const next = makeCounter();10console.log(next());11console.log(next());
The program calls makeCounter and waits for what it returns.
From outside, count cannot be reached: the file has no name to read it by. The count changes only through calls of next, and other code cannot spoil it by accident.
Two functions from one call
Two functions created in the same call see the same variables. It is one note clipped to two cards at once:
function makeScore() {
let score = 0;
return {
add: (points) => { score += points; },
get: () => score
};
}
const game = makeScore();
game.add(5);
game.add(3);
console.log(game.get()); // 8Let's go step by step:
- The
makeScorecall creates one variablescoreand two arrow functions. - Both functions go into the properties
addandgetof the returned object. game.add(5)andgame.add(3)change that veryscore.game.get()reads the same variable and prints8.
Common mistake
The count variable is declared inside the returned function:
function makeCounter() {
return () => {
let count = 0; // ✗
count += 1;
return count;
};
}
const next = makeCounter();
console.log(next(), next()); // 1 1There is no error, but the count does not grow. Now let count = 0 runs on every call of next, and every call starts from zero. Declare count in the body of makeCounter, before return: then there is one variable for the whole counter.
Key points
- A closure is a function together with the variables of the place where it was created; those variables live on after the outer call ends.
- A closure holds the variable itself, not a copy, so changes are kept between calls.
- Each call of a factory creates new variables, while functions from one call share the same ones.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.