Functions: naming and reusing behaviour · Lesson 20 of 77 · concept

Closures: functions that remember

About 55 minutes.

By the end of this lesson you can

  • Explain in one sentence what a closure is, in words someone else would follow.
  • Write a function that builds and returns another function.
  • Explain why the inner function still works after the outer one has finished.
  • Show that two functions built by the same factory keep separate, independent state.
  • Use a closure to keep a value private and reachable only through the function you were handed.

A function can return a function

Explanation

Lesson four established that a function is a value. Lesson three established that a function can return a value. Put those two facts together and you get something that looks like a trick the first time you see it: a function that returns a function.

The outer function is usually called a factory. You call it, and instead of a number or a string you get back a small machine that you can call later, as many times as you like. The factory runs once. The thing it made runs whenever you want it to.

This is not an exotic corner of the language. It is how a great deal of ordinary JavaScript is built, and it is the mechanism behind most of what the later modules do with collections.

What a closure actually is

Explanation

Here is the whole idea in one sentence: a closure is a function together with the outside variables it was created next to, kept alive and carried around as a pair.

Lesson five said that a function's local variables disappear when the call ends. That is true unless something still refers to them. If the outer function returns an inner function that mentions one of those locals, the inner function has not finished with it, so it is not thrown away. The variable outlives the call it was born in, and the only thing left in the program that can reach it is that inner function.

Two consequences follow directly and are worth stating plainly. The inner function keeps working after the outer one has returned. And it holds a live reference to the variable, not a snapshot of its value — so if the variable changes, the inner function sees the new value, and if the inner function changes it, the change sticks for next time.

The backpack

An analogy

When a function is created, it packs a backpack. Into it go the variables around it at that moment — not copies, the actual variables — and the function carries that backpack everywhere it goes, including out of the room where it was made.

So when a factory hands you a function, you are also being handed the backpack, whether you think about it or not. That is why the returned function can still count: the number is in the backpack it is carrying, and nobody else has a way to reach into it.

Two functions from the same factory carry two different backpacks, because each call to the factory created a fresh set of variables to pack. This is the part people expect to work the other way, and it is worth checking against the first worked example below until it feels obvious.

Every call to the factory makes a new one

Explanation

Calling the factory twice does not produce two references to one machine. It produces two machines, each with its own private state, running the same steps over different data.

This is the property that makes closures useful rather than merely clever. One factory called makeCounter can supply a counter for every page, every user or every game, with no shared bucket for them to interfere through, and no arrangement between them needed.

Privacy without any ceremony

Why it matters

A variable held in a closure cannot be read or written from outside. There is no name for it out there. The only way to interact with it is through the function you were given, which means the factory decides exactly what is allowed — and that is a genuine guarantee, not a convention.

It is worth appreciating how little this costs. No special keyword, no declaration of intent: privacy falls out of the scope rules you already learned in lesson five. Nothing outside a block can see inside it, and a closure is just that rule still holding after the block has finished.

Two things that go wrong early

A common mistake

Returning the result instead of the function. Writing return increment(); calls the inner function immediately and hands back its number, so the caller gets 1 rather than a machine. The fix is one pair of brackets: return increment; hands over the function itself. If your factory seems to return a number, this is why.

Expecting a snapshot. The backpack holds the variable, not a photograph of its value. A function created while a counter is 3 does not remember 3 — it remembers the counter, and will report whatever it holds when the function is finally called. Most of the time that is exactly what you want; when it surprises you, this is the reason.

Recap

Recap

A function can return a function, and the returned function keeps a live link to the variables it was created among — that pairing is the closure. Those variables survive the outer call because something still refers to them, so a returned counter can keep counting. Each call to the factory produces an independent set, and the variables are unreachable from outside, which makes them genuinely private.

Worked examples

Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.

A counter that keeps counting

The example everything else builds on. One factory, two counters, four calls — watch the second counter start from 1 while the first carries on from where it was.

1function makeCounter() {2  let count = 0;3  function increment() {4    count = count + 1;5    return count;6  }7  return increment;8}9 10const next = makeCounter();11console.log(next());12console.log(next());13 14const other = makeCounter();15console.log(other());16console.log(next());
  • line 2

    A local variable of makeCounter. By lesson five's rules it should vanish when the call ends — and it would, if nothing still referred to it.

  • line 7

    The function itself is returned, not called. No brackets here. This is the line that keeps count alive.

  • line 10

    makeCounter has now finished and returned. Its local count is still alive because next refers to it.

  • line 14

    A second call to the factory creates a brand new count, entirely separate from the first.

  • line 15

    So this prints 1, not 3. Different backpack.

  • line 16

    And the first counter is undisturbed: it prints 3, carrying on from its own 2.

What it prints

1213

A factory that bakes in a setting

The most common real use of a closure: a parameter of the outer function is remembered by the inner one, so you can produce specialised versions of a general operation.

1function makeAdder(amount) {2  return function (value) {3    return value + amount;4  };5}6 7const addTen = makeAdder(10);8const addHalf = makeAdder(0.5);9 10console.log(addTen(5));11console.log(addHalf(5));12console.log(addTen(addHalf(1)));
  • line 1

    A parameter is a local variable too, so it can be captured by a closure exactly like any other.

  • line 2

    The inner function is created and returned in one step. It uses amount, which belongs to the call of makeAdder that created it.

  • line 8

    A second call, a second amount, a second independent function. Neither can see the other's amount.

  • line 12

    They are ordinary functions, so they compose: addHalf(1) is 1.5, and addTen of that is 11.5.

What it prints

155.511.5

The captured value is genuinely out of reach

Proof that a closed-over variable is private. The returned function can use it; the surrounding program has no name for it at all.

1function makeSecretHolder() {2  let secret = 42;3  return function reveal() {4    return secret * 2;5  };6}7 8const doubled = makeSecretHolder();9console.log(doubled());10console.log(typeof secret);
  • line 2

    secret lives in the factory's own block and is never returned, printed or assigned anywhere outside.

  • line 9

    The returned function can still use it: 42 doubled is 84.

  • line 10

    Out here there is no name secret at all, so typeof answers "undefined". The only route to that value is through the function you were handed.

What it prints

84undefined

Check yourself

Not scored, not stored. Getting one wrong is the useful part.

Two functions come out of the same factory. What does this print?

function makeTicker() {  let ticks = 0;  return () => {    ticks = ticks + 1;    return ticks;  };}const a = makeTicker();const b = makeTicker();console.log(a());console.log(a());console.log(b());

Inside a factory, what is the difference between return increment; and return increment(); ?

Why can a returned inner function still use a variable from the outer function that has already finished?

Write it yourself

Exercise · core · 5 tests

A factory that makes multipliers

makeMultiplier(factor) should return a function. That returned function takes one number and gives back that number multiplied by the factor the factory was called with. So makeMultiplier(3) produces a tripler: call it with 4 and you get 12. The starter returns the factor itself, which is not a function — fix that.

What your code must do

makeMultiplier(factor) returns a function that has not been called yet. Calling that returned function with a number returns the number times factor. The factor may be negative or fractional. Two functions made by two separate calls to makeMultiplier each keep their own factor and do not interfere. Everything is returned; nothing is printed.

Define makeMultiplier at the top level of your code — the tests call it by name.

Runs on your device in a sandbox with no network and no access to this page.

Exercise · stretch · 5 tests

Give each counter its own count

The starter almost works: makeCounter() returns a function that counts up. The problem is where count is declared. Because it sits at the top level, every counter shares it, so a second counter carries on from the first one's number instead of starting fresh. Move the count so that each call to makeCounter gets its own, and so that no variable called count exists at the top level at all.

What your code must do

makeCounter() returns a function. Calling that function returns 1, then 2, then 3, and so on for that counter alone. Counters made by separate calls to makeCounter are completely independent: using one never changes another's number. After all of this, there is no top-level name called count — the number is reachable only through the returned function.

Define makeCounter at the top level of your code — the tests call it by name.

Runs on your device in a sandbox with no network and no access to this page.

Worth remembering

  • What is a closure, in one sentence?

    A function together with the outside variables it was created next to, kept alive and carried as a pair — so it still works, with a live link to those variables, after the outer call has finished.

  • Two functions come from the same factory. Do they share the factory's local variables?

    No. Each call to the factory runs its body again and creates a fresh set, so each returned function carries its own. That independence is what makes factories useful.

Check this lesson against the source

We wrote the explanation; we did not invent the facts. This is the page that backs them.

MDN — Closures (JavaScript Guide)

Check there: The guide defines a closure as a function bundled together with references to its surrounding state, states that a closure is created every time a function is created, and shows that the inner function keeps access to the outer function's variables after the outer function has returned.

MDN — Functions (JavaScript Guide)

Check there: The section on nested functions and closures.

Prefer to read the source in a structured breakdown? Turn this doc into a breakdown

0 of 2 exercises in this lesson solved on this device

Consent version 2026-07-31.1

Cookie preferences