Skip to content

Closures in JavaScript

You’ve learned how to write functions, and in the last article you saw how functions can take other functions or return them. But there’s a question we skipped along the way.

When a function returns another function, the outer function is done running. You’d expect its variables to be gone. So, can the inner function still use them?

function parent() {
    const x = 10;
    return function () {
        console.log(`Value of x is ${x}`);
    };
}
const p = parent();
p(); // will the inner function be able to access x's value?

Intuitively, you might expect the answer to be No. But in reality, the answer is Yes!

This works because of Closures. They’re one of the most talked about concepts in JavaScript, and they explain some really important things about functions, memory, and the language itself.

Before we get to closures, let’s look at what normally happens to variables declared inside a function:

function hello() {
    const message = "Hello";
}
hello();
console.log(message);

If you try running this code, you will get an error Uncaught ReferenceError: message is not defined. You will agree that since message is declared inside the function, code outside the function can’t see it.

There’s one more thing happening here. message is created when hello is called, and once the function finishes running, JavaScript throws it away, since nothing needs it anymore.

Keep this in mind. Now let’s understand the concept of closures with an analogy.

Imagine a car factory that builds cars. Every car that leaves the factory gets two things:

  • a license number, which is unique to that car
  • an odometer, which starts at 0 km

Once the car is sold, the new owner starts driving it, and every trip adds to the odometer.

A few years later, the car factory shuts down. Does anything happen to the cars it manufactured?

No. They still drive and their odometers keep counting from where they left off. Each car also has its own odometer, so driving one car doesn’t change the reading on another.

Now look at the code below:

function carFactory(licenseNumber) {
    let odometer = 0;

    return function drive(km) {
        odometer = odometer + km;
        console.log(`${licenseNumber} has driven ${odometer} km`);
    };
}

const carA = carFactory("JK-02-1234");
const carB = carFactory("JK-02-5678");

carA(10); // JK-02-1234 has driven 10 km
carA(5);  // JK-02-1234 has driven 15 km
carB(7);  // JK-02-5678 has driven 7 km

In the above example, you can see that:

  1. carFactory is our car factory. It’s a higher order function, since it returns a function.
  2. Every time carFactory runs, it creates a new odometer starting at 0, and builds a new car, which is the drive function it returns.
  3. licenseNumber is the car’s license number. It’s passed in when the car is built and stays with that car.
  4. carA and carB are two different cars, each built by its own call to carFactory.
  5. When we call carA(10), the odometer goes up and the output is 10 km. When we call carA(5), it continues from where it left off and the output is 15 km.
  6. When we call carB(7), the output is 7 km, not 22 km, because carB has its own odometer. Driving carA didn’t change it.

Cool! Now remember the hello example from earlier. odometer is declared inside carFactory, just like message was declared inside hello. So once carFactory finishes building a car, odometer should be thrown away. In our analogy, this is the factory shutting down.

Makes sense? Based on that, drive shouldn’t be able to use odometer anymore, because the outer function has already finished running by then.

Car factory closure example

However, in reality, drive can still access odometer, and this is called a closure. The outer function did finish running, but since the inner function still needs the variable, JavaScript doesn’t throw it away. It keeps the variable itself alive in memory, not a copy of its value, which is why the odometer keeps counting from where it left off.

The same thing happened in our very first example. parent finished running, but p() could still access x, because the returned function still needed it.

A closure is a function that keeps access to the variables from the place where it was written, even after that outer function has finished running.

What is a closure

You’ve already seen two closures in this article:

  • drive is a closure. It keeps access to odometer and licenseNumber from carFactory.
  • The function stored in p is a closure. It keeps access to x from parent.

Let’s look at one more simple example:

function makeGreeting(greeting) {
    return function (name) {
        console.log(`${greeting}, ${name}!`);
    };
}

const sayHello = makeGreeting("Hello");
const sayNamaste = makeGreeting("Namaste");

sayHello("Riya");   // Hello, Riya!
sayNamaste("Aman"); // Namaste, Aman!

In the above example, you can see that:

  1. makeGreeting takes a greeting and returns a function that takes a name.
  2. makeGreeting("Hello") finishes running, but the function it returns, sayHello, still remembers that greeting is "Hello".
  3. sayNamaste is a separate closure with its own greeting, which is "Namaste".
  4. When we call sayHello("Riya"), it uses its remembered greeting along with the name we pass in.

Cool! You now understand closures. Now you might be wondering: What are these even used for?

Closures aren’t just a neat trick. You’ll find them all over real JavaScript code. Here are a few common uses:

Variables inside a function can’t be reached from outside. With a closure, you can let other code use that data only in the ways you allow.

function createBankAccount() {
    let balance = 0;

    return {
        deposit(amount) {
            balance = balance + amount;
        },
        getBalance() {
            return balance;
        }
    };
}

const account = createBankAccount();
account.deposit(100);

console.log(account.getBalance()); // 100
console.log(account.balance);      // undefined

deposit and getBalance are closures over balance. Nobody can change balance directly; they have to go through deposit.

You already saw this with makeGreeting. One function can create many similar functions, each remembering its own settings:

function multiplyBy(x) {
    return function (y) {
        return x * y;
    };
}

const double = multiplyBy(2);
const triple = multiplyBy(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

Remember multiplyBy from the Higher Order Functions article? It worked because of closures. double remembers x as 2, and triple remembers x as 3.

When you pass a function to be called later, like with setTimeout, it still has access to the variables around it:

function greetAfterDelay(name) {
    setTimeout(function () {
        console.log(`Hello ${name}`);
    }, 1000);
}

greetAfterDelay("Riya"); // Hello Riya (after 1 second)

greetAfterDelay finishes running straight away, but the callback still remembers name when it runs a second later.

Now you know why a function can still use variables from a function that has already finished running.

You learned how to:

  • Understand what normally happens to a function’s variables once it finishes running
  • Explain what a closure is
  • Use closures to keep data private, create custom functions and remember values in callbacks

Whenever a function uses a variable from outside itself, it holds on to that variable for as long as it needs it. A closure is just a function that remembers where it came from.