Going DeeperPro· 45 min read

Closures

A function that remembers the variables around it, even after the outer function has finished. Strange at first, but the secret behind counters, private data and most event handlers — and the most-asked JS interview question.

What you will learn

  • Define a closure in plain words
  • Build a counter that keeps private state
  • Recognise closures you already use

A function with a memory

Normally, when a function finishes, its local variables disappear. But if that function returns another function (or hands one off as a handler), the inner function keeps a live link to the variables it was born with — even after the outer function is long gone. That bundle of "function + the variables it remembers" is called a closure.

A simple picture: every function is born carrying a little backpack of the variables that surrounded it. Wherever the function travels, it carries that backpack and can still read and update what is inside.

A counter that keeps its own count

The classic example is a counter factory. makeCounter creates a private count and returns a small function that increases and reports it. The count is not a global — it lives only inside the closure.

The inner function remembers count
<script>
  function makeCounter() {
    let count = 0;                 // private to this counter
    return function () {
      count = count + 1;           // remembers and updates count
      return count;
    };
  }

  const next = makeCounter();
  document.write(next() + ", ");   // 1
  document.write(next() + ", ");   // 2
  document.write(next());          // 3
</script>
Live preview

Walk through the magic:

  1. makeCounter() runs, creates count = 0, and returns the inner function. We store that returned function in next.
  2. Even though makeCounter has now finished, its count did not vanish — the returned function kept it in its backpack (closure).
  3. The first next() bumps count to 1 and returns 1.
  4. The second next() remembers the previous 1 and bumps it to 2; the third returns 3.
  5. The count is private — no outside code can touch it directly, only through next.

Note: Output: 1, 2, 3

And because each call to makeCounter builds a fresh backpack, two counters are completely independent:

Each counter has its own private state
<script>
  function makeCounter() {
    let count = 0;
    return () => ++count;
  }
  const a = makeCounter();
  const b = makeCounter();   // a separate, independent counter
  document.write(a() + ", " + a() + " | " + b());
</script>
Live preview

Calling makeCounter() twice makes two separate closures, each with its own count. So a goes 1 then 2, while b — untouched by a — starts fresh at 1. This independence is exactly why closures are used to store private, per-instance state.

Note: Output: 1, 2 | 1

You have already been using closures

  • Event handlers: a click handler that uses a variable from the surrounding code is a closure — that is how it "remembers" the value when it later fires.
  • Private data: closures let you hide variables so only chosen functions can change them — true encapsulation without a class.
  • Callbacks & timers: a function passed to setTimeout or map closes over the variables around it.

Tip: Interview-ready definition: "A closure is a function bundled together with references to the variables from the scope where it was created, so it can keep using them even after that scope has returned." Practise saying it with the backpack picture.

Watch out: A subtle classic bug: a var inside a loop is shared by every closure made in that loop, so they all end up seeing the final value. Using let (which is block-scoped — a fresh binding each iteration) fixes it. This is a favourite interview trap.

Q. In makeCounter, why does count keep its value between calls to the returned function?

Answer: The returned inner function forms a closure over count — it keeps a live reference to that variable, so the value persists across calls even though makeCounter has finished.

✍️ Practice

  1. Build a makeCounter and prove that two counters keep separate counts.
  2. Write a makeMultiplier(x) that returns a function multiplying its argument by the remembered x (e.g. double = makeMultiplier(2)).

🏠 Homework

  1. Create a tiny "bank account" using a closure: a function that holds a private balance and returns deposit/withdraw functions — no outside code can read the balance directly.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →