Bitsy Explains
Home/JavaScript, Explained Like You're Five/Lesson 15

Closures in JavaScript

A closure is a function that remembers the variables from where it was made, even after that outer function has finished. Scary name, simple idea.

Lesson 15 of 203 min readVideo
Video thumbnail: Closures Watch the 90-second video
Explain like I'm five

A little function is made inside a bigger function, which is its house. When it leaves, it packs a backpack with the variables it needs. Even after the house is gone, it still has its backpack. And what is inside is the real variable, not a photo of it.

A counter that remembers

makeCounter creates a variable called count, then gives back a little function that adds one to count.

function makeCounter() {
  let count = 0;
  return () => ++count;
}
const counter = makeCounter();
console.log(counter()); // prints: 1
console.log(counter()); // prints: 2

++count adds one to count and gives back the new value. By the time you call counter(), makeCounter has already finished running. Its house is gone. But the little function still remembers count, and each call bumps it up. That memory is a closure.

JavaScript keeps count alive because the little function still needs it to do its job. It won't throw away variables while something still uses them.

The real variable, not a photo

The backpack holds the live variable itself. If two functions are made in the same house, they share the same count, and each one sees the other's changes.

function makeCounter() {
  let count = 0;
  return {
    add: () => ++count,
    peek: () => count,
  };
}
const counter = makeCounter();
counter.add();
counter.add();
console.log(counter.peek()); // prints: 2

peek didn't save a copy of count when it was made, when it was still 0. It reads the current value every time.

It's private

Nobody else can touch count. Try to use it from outside, and you get an error, because count only exists inside makeCounter.

function makeCounter() {
  let count = 0;
  return () => ++count;
}
const counter = makeCounter();
console.log(count);

This throws ReferenceError: count is not defined. The only way to change count is through the little function. That makes closures a simple way to keep data private and stop other code from messing it up by accident.

The twist: a new backpack every time

Every call to makeCounter runs the function again, so it creates a brand new count and packs a brand new backpack.

function makeCounter() {
  let count = 0;
  return () => ++count;
}
const a = makeCounter();
const b = makeCounter();
a();
a();
console.log(b()); // prints: 1
console.log(a()); // prints: 3

a and b each have their own count. They don't share.

Where you'll meet closures

You use closures all the time, often without noticing. Any function that uses a variable from outside itself is one. That includes callbacks for setTimeout, click handlers, and functions you pass to map or filter that read a value from the surrounding code. They are also how you build things like counters, caches, and "make me a function" helpers.

Here is a small one you might write without thinking of it as a closure.

function remindLater(task) {
  setTimeout(() => console.log("Time to " + task), 100); // prints: Time to feed the cat
}
remindLater("feed the cat");

remindLater finishes right away. A tenth of a second later the arrow function runs, and it still has task in its backpack.

Once nothing uses the arrow function any more, JavaScript is free to clean up task too. The backpack only lasts as long as someone still carries it.

Rule of thumb

A function remembers the variables from where it was made, and that is a closure.

Common mistake

Using var in a loop with setTimeout: for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)) prints 3 three times, because all three callbacks share one i. Use let, which gives each loop round its own i.

Check yourself

Two counters are made with const a = makeCounter(); const b = makeCounter();. You call a() twice. What does b() return?