Closures
Objectives
By the end of this chapter, you should be able to:
- Explain, in your own words, what a closure is
- Trace through a closure example by hand and predict its output
- Explain why two closures created from the same function stay independent of each other
💡 Why this matters: Closures show up constantly in real JavaScript, in code that hides internal state, in code that configures a function ahead of time, and (later in this course) in code that reacts to clicks and events. They also trip up almost every beginner at first, so we’re going to go slowly and prove every claim with running code.
What Is a Closure?
A closure is a function that remembers the variables from the place it was created, even after the outer code that created it has already finished running.
That definition is abstract, so let’s make it concrete with the smallest example that shows it clearly: a counter.
function makeCounter() {
let count = 0;
function increment() {
count = count + 1;
return count;
}
return increment;
}
Walk through what happens step by step:
makeCounterruns. It creates a local variablecountset to0.- Inside
makeCounter, it defines a functionincrement, which reads and updatescount. makeCounterreturnsincrementand then finishes running.
Normally, once a function finishes, its local variables are gone. But increment still refers to count, so JavaScript keeps count alive, attached to increment, for as long as increment might still be called. That attachment, a function plus the variables it remembers, is the closure.
Proving It Actually Remembers
Let’s call makeCounter and use the function it returns, several times in a row:
function makeCounter() {
let count = 0;
function increment() {
count = count + 1;
return count;
}
return increment;
}
const counter1 = makeCounter();
console.log(counter1());
// 1
console.log(counter1());
// 2
console.log(counter1());
// 3
Each call to counter1() runs increment again. count isn’t reset to 0 between calls, because counter1 still holds the closure over the exact same count variable that was created back when makeCounter() ran once. That’s the “remembers a variable after the outer function has finished” part, proven: makeCounter finished running before we ever called counter1() the first time, yet the value kept growing across calls.
Each Closure Gets Its Own Copy
Now call makeCounter() a second time, creating a brand new counter:
function makeCounter() {
let count = 0;
function increment() {
count = count + 1;
return count;
}
return increment;
}
const counter1 = makeCounter();
console.log(counter1());
// 1
const counter2 = makeCounter();
console.log(counter2());
// 1
counter2 starts back at 1, completely unaffected by everything counter1 has done. Every call to makeCounter() creates a fresh count variable, and the closure returned each time is tied only to its own copy. counter1 and counter2 never interfere with each other.
A Second Example: Remembering a Parameter
Closures don’t just remember variables declared with let or const inside the outer function, they also remember parameters.
function makeGreeter(name) {
return function () {
console.log("Hello, " + name);
};
}
const greetErin = makeGreeter("Erin");
const greetJordan = makeGreeter("Jordan");
greetErin();
// Hello, Erin
greetJordan();
// Hello, Jordan
greetErin and greetJordan are two different closures, each remembering the name it was created with. Calling greetErin() never looks at "Jordan", and calling greetJordan() never looks at "Erin", because each function has its own remembered copy of name from when makeGreeter was called.
Try It
- Trace
makeCounterby hand: if you calledcounter1()four times in a row, what would the four logged values be? Then run it and check. - Create two counters,
counterAandcounterB, frommakeCounter. CallcounterA()twice andcounterB()once, in that order, logging each result. Predict the three values before you run it. - Write your own
makeGreeter-style function calledmakeFarewell(name)that returns a function logging"Goodbye, "followed byname. Create two farewells for different people and call both.
Recap
- A closure is a function that remembers the variables from where it was created, even after the outer function has finished running.
- Calling the outer function again creates a brand new, independent set of remembered variables, closures don’t share state with each other.
- Closures can remember both local variables and parameters from the outer function.
Next lesson: recursion, a function that solves a problem by calling itself on a smaller version of that same problem.