completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Closures in JavaScript

Understand how a function keeps access to its variables, even after the function that created them has finished.

Three things to take away
  • 01
    Variables that stayCall a function after the function that made it has ended.
  • 02
    Shared or separate?Change one line and see whether two counters share one count.
  • 03
    A private countTry to change a cart’s hidden item count from outside.

What is a closure?

A closure is a function that keeps using the variables from the place where it was written. Here is what that means:

  • An outer function runs and makes some variables.
  • Inside it, an inner function is made. The inner function uses those variables.
  • The outer function ends. The inner function can still read and change those variables later.

We'll start with a function that remembers a short message. Then we'll use a counter to see that a remembered variable can change, too.

The short version

A function remembers the variables from where it was written, not from where it is called.

Real-life analogyA backpack packed at home

You pack a backpack at home and take it with you. Later, far from home, you can still open it and use what is inside.

In real life: You pack a backpack at home
In JavaScript: The outer function makes its variables
In real life: You take the backpack when you leave
In JavaScript: The inner function keeps those variables
In real life: You open it later, somewhere else
In JavaScript: The inner function reads them when it is called later
In real life: A new trip gets a new backpack
In JavaScript: Each new call of the outer function makes new variables

You don't write anything special to make a closure. Every JavaScript function keeps a link to the variables around the place where it was written. Returning a function from another function just makes this easy to see.

You can run the examples below one step at a time. Click the blue line, or press Step, to run the next step. Each step says what happened: a jump into a function, a variable change, or a return. Press Back to go one step back and compare.

Start with a nested function

A nested function is a function written inside another function. The inner function can read the variables of the outer function. This rule is called lexical scope. "Lexical" means "about where the code is written". So a function can use the variables around the place where it is written.

Here is a small example. makeGreeter takes a name and returns a new function. The new function makes a greeting from that name.

A greeter that remembers a namePop out in the code editor (opens in a new tab)JavaScript
function makeGreeter(name) {
  return () => `Hello, ${name}!`;
}

const greetAsha = makeGreeter("Asha");
console.log(greetAsha());

Line by line:

  1. Line 1 starts makeGreeter. Its parameter, name, is a variable that belongs to this call.
  2. Line 2 returns a new arrow function. The arrow function uses name, but it does not run yet.
  3. Line 5 calls makeGreeter("Asha"), so name is "Asha". The call returns the arrow function, and we save it as greetAsha. Now makeGreeter has ended.
  4. Line 6 calls greetAsha(). It still reads name, so it prints Hello, Asha!.
The function greetAsha has an arrow to a box called name, which holds Asha. The box is kept after makeGreeter ends. greetAsha prints Hello, Asha!greetAsha()printsHello, Asha!reads"Asha"namekept after makeGreeter ends
makeGreeter("Asha") made the box name and then ended. The function it returned, greetAsha, still reads that box. So greetAsha() prints Hello, Asha!.

The step-through below uses a very similar function, createGreeting. It takes a message and returns a function called greet, which gives the message back. You can change the message on line 7. Step into the first call and watch it return. Then follow the second call back to the message.

Follow a closure, one line at a time
Step 0 of 5Ready
Your turn: follow the blue line

Click the blue line, or press Step. Start at the call on line 7. A function's body runs only when the function is called.

Running in
  1. script
Next: line 7
function createGreeting(message) {  return function greet() {    return message;  };} greet();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.

When you change the text, the walkthrough starts again. Press Step or click the blue line to follow your new message.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Keep these two moments apart:

  1. Line 7 makes the function. createGreeting("Hello!") runs and returns a function. We save that function as greet. The call to createGreeting has now ended.
  2. Line 8 calls the function. greet() runs the inner function. It still reads message from the call that has already ended.

That kept access to message is the closure. message is not a global variable (a variable that all code can see). And we did not pass it to greet().

See a closure in action

INTERACTIVE

The greeting only reads a variable. A counter goes one step further. It changes a variable, and the next call sees the new value.

makeCounter makes a variable, count, that starts at 0. It returns a function that adds 1 to count and returns the new number. Press Step to follow each call into the function. Watch the value of count appear beside line 2. When the walkthrough ends, use the buttons to call a() or b() again.

Step inside a counter
Step 0 of 15Ready
Your turn: follow the blue line

Step into each call. Watch count appear beside the code, stay after makeCounter() ends, and change.

Running in
  1. script
Next: line 9
function makeCounter() {  let count = 0;  return function increment() {    count += 1;    return count;  };} a();a(); const b = makeCounter();b();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
What did not happen

The second a() did not run makeCounter() again. It ran the returned function. That function changed the same count from 1 to 2.

Each call makes new variables

A function that makes and returns other functions is often called a factory. makeCounter is a factory. Each call to makeCounter() makes a new count, starting at 0.

The code below makes two counters, first and second.

Two counters, two countsPop out in the code editor (opens in a new tab)JavaScript
function makeCounter() {
  let count = 0;
  return () => ++count;
}

const first = makeCounter();
const second = makeCounter();
console.log(first(), second(), first());

Line by line:

  1. Lines 1 to 4 are the same makeCounter as before, written shorter. ++count adds 1 to count and gives back the new value.
  2. Line 6 calls makeCounter(). This call makes one count, and first uses it.
  3. Line 7 calls makeCounter() again. This call makes a second count, and second uses it.
  4. Line 8 calls first(), then second(), then first() again. It prints 1 1 2. second() gives 1 because its count is new.
Left: the function first has an arrow to its own count box, which holds 2 because first was called twice. Right: the function second has an arrow to a different count box, which holds 1 because second was called once.first()2countcalled twice: 2second()1countcalled once: 1
Each makeCounter() call makes a new count box. first and second never share. After line 8, first's box holds 2 and second's box holds 1.

But const b = a is different. It does not call makeCounter(). It gives the same function a second name. Two names for one function means one count.

In the step-through below, you choose what line 10 says. Pick one option. Then follow a(), b(), and a() again. With const b = a, notice that JavaScript never goes into makeCounter() a second time.

Follow a new counter or the same one
Step 0 of 16Ready
Your turn: follow the blue line

Step into each call. Watch count appear beside the code, stay after makeCounter() ends, and change.

Running in
  1. script
Next: line 9
function makeCounter() {  let count = 0;  return function increment() {    count += 1;    return count;  };} const b = makeCounter();a === b;a();b();a();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose line 10, then watch which lines run

When you change the line, the walkthrough starts again. With b = a, watch JavaScript skip the second call to makeCounter().

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The rule to remember: a new call to the factory makes new variables. A second name for the same function shares the old ones.

A practical use: a private count

Imagine a shopping cart. Its item count should change only through an add method. Nothing else should be able to set it. A closure can do this. The count lives inside a function, so it is private: code outside cannot see it or change it directly.

createCart makes a variable, items. It returns an object with two methods (functions stored on an object): add and getCount. Step through the code. First watch items being made. Then watch add() change it. At line 16, use Back and Step to compare the object's new items property with the private items variable.

Step inside the private cart
Step 0 of 9Ready
Your turn: follow the blue line

Click the blue line to follow the cart from the first line to the last. Each step explains one thing that happens.

Running in
  1. script
Next: line 14
function createCart() {  let items = 0;  return {    add() {      items += 1;      return items;    },    getCount() {      return items;    }  };} cart.add();cart.items = 99;cart.getCount();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

add and getCount were made in the same call to createCart. So they share one items variable. Neither method has its own copy.

A cart holds two methods, add and getCount. One arrow goes from the cart to a box called items, which holds 1. Both methods use this box. It is hidden inside createCart.add()getCount()cartboth use1itemshidden inside createCart
add() and getCount() were made in the same createCart() call, so both use the one items box. Code outside has no name for that box. Only the two methods can reach it.

Line 16, cart.items = 99, adds a new property called items to the object. It does not touch the private variable, so getCount() still returns 1. Calling createCart() again would make a new cart with its own items.

A Pay button that works only oncePop out in the code editor (opens in a new tab)JavaScript
function once(action) {
  let used = false;
  return () => {
    if (used) return "Already paid";
    used = true;
    return action();
  };
}

const payOnce = once(() => "Payment sent");
console.log(payOnce(), payOnce());

Here is another private variable. once takes an action and returns a new function. Its variable, used, starts as false.

  1. The first call sees that used is false. It sets used to true and runs the action, which returns "Payment sent".
  2. The second call sees that used is true. It returns "Already paid" and does not run the action.

So line 10 prints Payment sent Already paid.

A click handler that remembers a productPop out in the code editor (opens in a new tab)JavaScript
function makeProductHandler(productId) {
  return () => `Opening product ${productId}`;
}

const openNotebook = makeProductHandler("notebook-7");
console.log(openNotebook());

A click handler is a function that runs when someone clicks a button. On line 5, makeProductHandler("notebook-7") returns a handler that remembers the product ID. A real button could call openNotebook later. Here, line 6 calls it right away and prints Opening product notebook-7. No global variable is needed to hold the product ID.

There are three common places to keep a value that changes. Read each row as: "If I keep it here, this is where it lives, and this is who can change it."

Three places to keep a value that changes
Where you keep itWhere the value livesWho can change it
In a closureIn a variable inside the function call that made it.Only the functions made in that same call.
In a global variableIn a variable at the top level of the code.Any code that knows the variable's name.
In an object propertyIn a named property on an object.Any code that has the object.

You'll also see closures in these places:

  • Click handlers that remember a setting, like the product ID above.
  • Debounce helpers. A debounce helper waits until you stop typing before it runs. It keeps its timer in a closure.
  • Callbacks that use variables from the code around them.

Common misconceptions

A closure does not freeze a copy of each value. It keeps the variable itself. But a value that was already built from a variable does not update later. The step-through below shows both:

  • live() reads count each time it runs, so it sees the newest number.
  • saved() returns message. That string was built once, when count was 0.
Watch a variable change, but not a saved string
Step 0 of 11Ready
Your turn: follow the blue line

Watch the two variables beside lines 2 and 3. Step through the methods to see why only one result changes.

Running in
  1. script
Next: line 18
function createReaders() {  let count = 0;  const message = `Count is ${count}`;  return {    increment() {      count += 1;      return count;    },    live() {      return `Count is ${count}`;    },    saved() {      return message;    }  };} readers.increment();readers.live();readers.saved();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
A timer reads the newest valuePop out in the code editor (opens in a new tab)JavaScript
let status = "saving";
const report = () => console.log(status);
status = "saved";
setTimeout(report, 0);

setTimeout(report, 0) asks JavaScript to run report later, after the rest of the code. Line by line:

  1. Line 1 sets status to "saving".
  2. Line 2 makes report, a function that prints status.
  3. Line 3 changes status to "saved".
  4. Line 4 runs report later. By then status is "saved", so it prints saved.

report keeps the variable status, not the word "saving".

Two functions share one variablePop out in the code editor (opens in a new tab)JavaScript
function makeReaders() {
  let name = "Asha";
  return {
    read: () => name,
    rename: (nextName) => { name = nextName; },
  };
}

const readers = makeReaders();
const readNow = readers.read;
readers.rename("Mina");
console.log(readNow(), readers.read());

makeReaders makes one variable, name, set to "Asha". It returns two functions made in the same call: read and rename.

  1. Line 10 saves readers.read as readNow. It is the same function with a second name.
  2. Line 11 calls rename("Mina"). This changes the one shared name.
  3. Line 12 calls both readers. Both read the same name, so it prints Mina Mina.
A var loop and a let loopPop out in the code editor (opens in a new tab)JavaScript
for (var i = 0; i < 3; i += 1) {
  setTimeout(() => console.log("var", i), 0);
}

for (let j = 0; j < 3; j += 1) {
  setTimeout(() => console.log("let", j), 0);
}

Each loop sets up three timers. The timers run after both loops have ended.

  • The var loop has one i for the whole loop. All three callbacks read that one i, which is 3 by then. They print var 3 three times.
  • The let loop makes a new j for each pass. Each callback reads its own j. They print let 0, let 1, and let 2.
Left: three callbacks from the var loop all point to one shared i, which holds 3. They print 3, 3, 3. Right: three callbacks from the let loop each point to their own j, holding 0, 1, and 2. They print 0, 1, 2.for (var i ...)() =>() =>() =>i: 3one shared iprints 3, 3, 3for (let j ...)() =>j: 0() =>j: 1() =>j: 2a new j each passprints 0, 1, 2
The callbacks run after the loops end. With var, all three read the one i, which is 3 by then. With let, each pass of the loop gets a new j, so each callback reads its own number.

“A closure saves a copy of a value.”

No. It keeps the variable itself. In the step-through above, count changes, and live() sees the change. message stays the same because it is a string that was built once. Reading it again does not build it again.

“Every function gets its own separate variables.”

Not always. Functions made in the same outer call share its variables, like the two cart methods. Only separate calls to the factory make separate variables.

“You must return a function to make a closure.”

No. Returning a function is one way to use it later. Passing an inner function to a timer or a click listener works too. That function keeps its outer variables as well.

“A closure keeps its variables forever.”

No. When no code can reach the function any more, JavaScript can free its variables. But a callback that lives a long time can keep big objects in memory. So remove listeners and clear timers you no longer need.

When no code can reach the function

JavaScript frees memory that no code can reach any more. This is called garbage collection. A closure's variables stay while some function, listener, or timer can still use them. Once the last one is removed, JavaScript may free them. JavaScript decides when, so your code should not depend on the exact moment.

What about loops?

A var loop has one loop variable, and every callback shares it. A let loop makes a new loop variable for each pass. That is why delayed callbacks can print different results with var and with let.

Practice exercises

4 EXERCISES

The exercises go from reading a counter to building a small helper. Try each one before you open the hints and the worked solution.

Exercise 1 · Warm-upPredict a counter

Read the counter and predict both printed lines. makeCounter is called only once.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function makeCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}
const counter = makeCounter();
console.log(counter());
console.log(counter());

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite a greeter

    Write the small function shown in the starter code. Then check the greeting it gives for Asha.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function makeGreeter(name) {
      return function () {
        return "Hello, " + name + "!";
      };
    }
    const greet = makeGreeter("Asha");
    console.log(greet());

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix the loop

      First predict what the three callbacks return. Then find the one word that makes them all return the same number, and change it.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const callbacks = [];
      for (var index = 0; index < 3; index += 1) {
        callbacks.push(() => index);
      }
      console.log(callbacks[0]());
      console.log(callbacks[1]());
      console.log(callbacks[2]());

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeBuild a once helper

        Make a helper for a Pay button. It should run the action the first time only, and give a clear message on later calls.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function once(fn) {
          let used = false;
          return function () {
            if (used) return "Already paid";
            used = true;
            return fn();
          };
        }
        const pay = once(() => "Payment sent");
        console.log(pay());
        console.log(pay());

        Answer, then press Check. Spacing and letter case don’t matter.

          Check your understanding

          6 QUESTIONS

          Predict each output first. Then read why each choice is right or wrong.

          Predict before you runPop out in the code editor (opens in a new tab)JavaScript
          const counter = makeCounter();
          const alias = counter;
          
          counter(); // ?
          alias();   // ?
          counter(); // ?
          Closures quiz · 6 questionsScore: first tries count
          1. Question 1 of 6What does this counter code print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            function makeCounter() {
              let count = 0;
              return () => ++count;
            }
            const counter = makeCounter();
            const alias = counter;
            console.log(counter());
            console.log(alias());
            console.log(counter());

            Choose an answer to see the explanation.

          2. Question 2 of 6Which sentence best describes a closure?

            Choose an answer to see the explanation.

          3. Question 3 of 6What does getCount() print at the end?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            function createCart() {
              let items = 0;
              return { add() { items += 1; return items; }, getCount() { return items; } };
            }
            const cart = createCart();
            cart.add();
            cart.items = 99;
            console.log(cart.getCount());

            Choose an answer to see the explanation.

          4. Question 4 of 6What does the var loop print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const callbacks = [];
            for (var index = 0; index < 3; index += 1) {
              callbacks.push(() => index);
            }
            console.log(callbacks[0](), callbacks[1](), callbacks[2]());

            Choose an answer to see the explanation.

          5. Question 5 of 6Why do the two methods from one createCart() call share items?

            Choose an answer to see the explanation.

          6. Question 6 of 6What do live() and saved() print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            function createReaders() {
              let count = 0;
              const message = "Count is " + count;
              return {
                increment() { count += 1; return count; },
                live() { return "Count is " + count; },
                saved() { return message; },
              };
            }
            const readers = createReaders();
            readers.increment();
            console.log(readers.live());
            console.log(readers.saved());

            Choose an answer to see the explanation.

          Key takeaways

          • A closure is a function plus the variables from the place where it was written.
          • The outer function can end, and the function it returned can still use its variables.
          • A closure keeps the variable itself, not a frozen copy of its value.
          • Each call to a factory makes new variables. Functions made in the same call share them.
          • Closures give you private variables, run-once helpers, and click handlers that remember a setting.

          Remember the idea, not the long words.
          The outer function ends. Its variables can stay, as long as a function made inside it still uses them.

          CompleteFrontend Clear concepts. Working examples.