JavaScript Closures
Table of Contents + −
In the previous lesson, we learned how to bundle data and behavior together and hide the inside details. Now let’s look at closures, the feature that makes that kind of privacy possible.
🧠 What is a Closure?
A closure is a function that remembers and keeps access to variables from the place where it was created, even after the outer function that made it has finished running.
To see this, you first need the idea of an inner function. When you define a function inside another function, the inner function can read the outer function’s variables. We will cover the full rules of which variables a function can see in the next lesson on scope, but the part that matters here is simple: the inner function stays connected to those variables even after the outer function returns.
function makeGreeting(name) { // `name` belongs to the outer function return function () { return `Hello, ${name}!`; // the inner function still remembers `name` };}
const greetAlex = makeGreeting("Alex");console.log(greetAlex()); // Hello, Alex!Let’s walk through how the inner function holds on to name:
function makeGreeting(name)is the outer function, andnameis its local variable.- The
return function () { ... }line hands back an inner function that readsnamefrom the outer function around it. const greetAlex = makeGreeting("Alex")runs the outer function withnameset to"Alex", then stores the returned inner function ingreetAlex.greetAlex()calls that inner function later. Even thoughmakeGreetingalready finished, the inner function still reaches back to thenameit was created with and returnsHello, Alex!.
That held-on connection between the inner function and the outer variable is the closure.
🔢 The Classic Counter Example
The clearest way to feel a closure is a counter. The outer function defines a count variable and returns an inner function that increments it and gives it back.
function makeCounter() { let count = 0; // private variable
return function () { count = count + 1; return count; };}
const counter = makeCounter();console.log(counter()); // 1console.log(counter()); // 2console.log(counter()); // 3Let’s read it from the top and see how count survives between calls:
let count = 0creates a variable that lives insidemakeCounter.- The returned inner function does
count = count + 1andreturn count, so it changes and reads the samecountfrom the outer function. const counter = makeCounter()runs the outer function once and saves the inner function incounter.- Each
counter()call adds one to that samecountand returns the new value, so we get1, then2, then3.
The count is never reset, because the inner function remembers the one variable instead of making a new one. Nothing outside makeCounter can read or change count directly, which is exactly the kind of privacy we want.
🔒 Each Closure Keeps Its Own Copy
Every time you call the outer function, it builds a brand new variable and a brand new closure around it. Two counters do not share their counts.
const counterA = makeCounter();const counterB = makeCounter();
console.log(counterA()); // 1console.log(counterA()); // 2console.log(counterB()); // 1 → counterB has its own private countLet’s trace why the two counters don’t interfere:
makeCounter()runs twice, so it creates two separatecountvariables, one for each call.counterAcloses over the firstcount, andcounterBcloses over the secondcount.- Calling
counterA()twice raises its owncountto1then2. - Calling
counterB()uses a different rememberedcountthat started fresh at0, so it returns1.
Because each closure remembers its own variable, the counters count independently. This is what makes closures so useful for private state.
Private by default
A variable inside the outer function cannot be touched from the outside. The only way to change it is through the inner function you returned. That is data privacy with no extra tools.
🏭 Function Factories
Because each call to the outer function produces a fresh closure, you can use a function to build customized functions. This pattern is called a function factory.
function makeMultiplier(factor) { return function (value) { return value * factor; // `factor` is remembered };}
const double = makeMultiplier(2);const triple = makeMultiplier(3);
console.log(double(5)); // 10console.log(triple(5)); // 15Let’s see how each returned function remembers its own factor:
makeMultiplier(factor)is the outer function, andfactoris the value it closes over.- The inner function takes a
valueand returnsvalue * factor, reaching back to thefactorit was created with. const double = makeMultiplier(2)returns an inner function that remembersfactoris2.const triple = makeMultiplier(3)returns a separate inner function that remembersfactoris3.- So
double(5)gives10andtriple(5)gives15, each using its own rememberedfactor.
You wrote the logic once and stamped out two ready-made tools.
⚠️ Common Mistakes to Avoid
| Mistake | Problem | Solution |
| Expecting the variable to reset | count is not zeroed on each call; it keeps its last value | Remember the closure holds the same variable between calls |
| Thinking closures share state | Each call to the outer function makes its own private copy | Call the outer function once if you want one shared counter |
| Overusing closures | Wrapping everything in functions makes code hard to read | Use them only when you need private or remembered state |
Here is the reset mistake in code so the difference is clear.
// ❌ A new counter every time means it always returns 1console.log(makeCounter()()); // 1console.log(makeCounter()()); // 1
// ✅ Keep one counter so the count growsconst counter = makeCounter();console.log(counter()); // 1console.log(counter()); // 2Let’s compare the two halves line by line:
makeCounter()()calls the outer function and then immediately calls the returned inner function. EachmakeCounter()builds a brand newcountat0, so it always returns1.- In the second half,
const counter = makeCounter()runs the outer function once and keeps the returned inner function. - Calling that same
counter()twice reuses one rememberedcount, so it grows from1to2.
The fix is to hold on to a single counter instead of making a new closure on every line.
🔧 Try It Yourself!
- Write a
makeCounterfunction and call it once to get a counter, then call that counter three times. - Create two separate counters and confirm they count independently.
- Write a
makeMultiplierfactory and build adoubleand atriplefunction from it. - Write a function that remembers a setting, such as
makeGreeting("Sam"), and call the returned function.
🧩 What You’ve Learned
- ✅ A closure is a function that remembers variables from where it was created
- ✅ An inner function can use the outer function’s variables, even after the outer function finishes
- ✅ The counter example shows a private
countthat survives between calls - ✅ Each call to the outer function makes its own private copy
- ✅ Closures power data privacy and function factories
🚀 What’s Next?
Closures depend on which variables a function can see. Next we will study those rules directly. Let’s continue to Scope.