Closures: Functions With Backpacks

Grade 9, page 9 of 11. A three-minute read.

Now we get to one of the most powerful (and famous) ideas in JavaScript. It has a slightly scary name, closures, but the idea is surprisingly simple once it clicks.

Here's the key fact: a function remembers the variables from the place where it was created, even after that place is gone.

Let's see it in action:

JavaScript
function makeCounter() {
  let count = 0;

  return function () {
    count++;
    return count;
  };
}

const counter = makeCounter();

console.log(counter());
console.log(counter());
console.log(counter());

It prints

1
2
3

Let's slow down and think about how strange this is.

  1. We call makeCounter(). It creates a local variable count = 0, and then returns a little inner function. Then makeCounter is finished.
  2. Normally, when a function finishes, its local variables disappear. count should be gone!
  3. But when we call counter(), it happily adds one to count and returns it. And it keeps remembering the value between calls: 1, 2, 3.

The inner function has closed over the variable count. It carries count around with it, wherever it goes. That combination (a function plus the variables it remembers) is called a closure.

Think of it like thisThe backpack

When a function is created inside another function, it packs a backpack containing all the variables it can see at that moment. When the outer function finishes, the inner function walks away wearing its backpack. Whenever it runs later, it can reach into the backpack and use (or change) those variables. No one else can get into the backpack. Only that function.

Every call makes a new backpack

Each time you call makeCounter, you get a brand-new count and a brand-new function to go with it. They're completely independent:

JavaScript
const counterA = makeCounter();
const counterB = makeCounter();

console.log(counterA());
console.log(counterA());
console.log(counterA());
console.log(counterB());

It prints

1
2
3
1

counterA and counterB each have their own backpack, with their own count inside. Counting with one doesn't affect the other.

Why closures are useful

1. Private data

Notice that there's no way to change count from outside, except by calling the counter. You can't write counter.count = 1000. The variable is completely private. This is a great way to protect data from being messed with:

JavaScript
function createBankAccount(startingBalance) {
  let balance = startingBalance;

  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) {
        console.log("❌ Not enough money!");
        return balance;
      }
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    },
  };
}

const myAccount = createBankAccount(100);
console.log(myAccount.deposit(50));
console.log(myAccount.withdraw(500));
console.log(myAccount.withdraw(30));
console.log(myAccount.getBalance());
console.log(myAccount.balance);

It prints

150
❌ Not enough money!
150
120
120
undefined

The balance variable is locked safely in the backpack of those three methods. The only way to change it is through deposit and withdraw, which follow the rules. myAccount.balance doesn't exist, so nobody can cheat and give themselves a million dollars.

2. Function factories

A function that creates and returns other functions is sometimes called a factory. Closures let each factory-made function remember its own settings:

JavaScript
function makeGreeter(greeting) {
  return (name) => `${greeting}, ${name}!`;
}

const sayHello = makeGreeter("Hello");
const sayAkwaaba = makeGreeter("Welcome");
const sayYo = makeGreeter("Yo");

console.log(sayHello("Mia"));
console.log(sayAkwaaba("Omar"));
console.log(sayYo("Professor"));

It prints

Hello, Mia!
Welcome, Omar!
Yo, Professor!
JavaScript
function makeMultiplier(factor) {
  return (number) => number * factor;
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);
const tenTimes = makeMultiplier(10);

console.log(double(5), triple(5), tenTimes(5));

It prints

10 15 50

Each function remembers the factor it was made with.

3. Remembering things between clicks

When you start building web pages in Grades 11 and 12, you'll write functions that run whenever someone clicks a button. Closures are how those functions remember things (like how many times the button has been clicked) without using messy global variables.

Professor’s CornerYou've been using closures all along!

Here's a secret: every function in JavaScript is a closure. Whenever a function uses a variable from an outer scope (like showHeader using the global appName earlier in this grade), it's closing over that variable. Closures only feel special when the inner function outlives the outer function, like our counter. But the mechanism is exactly the same: functions can always see the variables from the place they were created.

Reading is step one

CurlyScript turns these notes into a game: short questions, an explanation after every answer, and a test at the end of each grade. Free, with nothing to sign up for.

Start learning