Skip to content
Theory · Closures and this · lesson 2 of 11

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:

JavaScript
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, Bob

Let's go through it line by line:

  1. makeGreeter("Hi") gets "Hi" in the parameter greeting, returns an arrow function and finishes.
  2. const hi holds that arrow function.
  3. makeGreeter("Hello") does the same with its own greeting, and the result goes into hello.
  4. hi("Ann") prints Hi, Ann, even though the call makeGreeter("Hi") ended long ago. The arrow function still knows its greeting.

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:

JavaScript
function makeCounter() {
    let count = 0;
    return () => {
        count += 1;
        return count;
    };
}

const next = makeCounter();
console.log(next()); // 1
console.log(next()); // 2
console.log(next()); // 3

The 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.

LiveA counter from a closurestep 1 of 10 · frames: 1

The program calls makeCounter and waits for what it returns.

The captured row shows the variables the closure holds

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:

JavaScript
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()); // 8

Let's go step by step:

  1. The makeScore call creates one variable score and two arrow functions.
  2. Both functions go into the properties add and get of the returned object.
  3. game.add(5) and game.add(3) change that very score.
  4. game.get() reads the same variable and prints 8.

Common mistake

The count variable is declared inside the returned function:

JavaScript
function makeCounter() {
    return () => {
        let count = 0; // ✗
        count += 1;
        return count;
    };
}

const next = makeCounter();
console.log(next(), next()); // 1 1

There 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.

Sign in another way