languages/javascript-closures-how-a-function-remembers-a-variable-after-the-func.md

JavaScript closures: how a function remembers a variable after the function that made it has finished

Programming languages · 5 min read ·

A function in JavaScript can still use a variable long after the function that created it has finished. That is called a closure, and you have already written dozens of them.

Press a button to make counters with the real `makeCounter()` code, and watch each one keep its own hidden `count` box. Then see why three timers in a `var` loop all print 3 while the `let` version prints 0, 1, 2. Every code sample was run, and the output shown is the real output.

Two boxes labelled counter a and counter b, holding the numbers 3 and 1, each with its own button a() and b()

Look at this small function. It makes a counter, and the counter remembers how many times you have called it:

function makeCounter() {
  let count = 0;
  return function () {
    count = count + 1;
    return count;
  };
}

const a = makeCounter();
console.log(a(), a(), a());   // 1 2 3

That output is real, from Node 22. But there is something odd here. count is a local variable: a variable that belongs to one call of makeCounter. When a function finishes, its local variables are normally thrown away. makeCounter finished the moment it returned. So how can a() still add to count afterwards?

The answer: a function carries its surroundings

In JavaScript, when you create a function inside another function, the new function keeps a hidden link to the variables that were in scope where it was written. The word scope just means "the places in the code where a name can be used". The inner function can see count because count is in the scope around it.

That function plus its link to the surrounding variables is called a closure. You never write the word closure in your code. Every function you create inside another function is one.

The important detail: the link is to the variable itself, not to a copy of its value at that moment. As long as something can still call the inner function, JavaScript keeps count alive. When nothing can reach the function any more, the variable can be cleaned up with it.

Try it: every call to makeCounter gets its own box

Below, each press of the first button runs the real makeCounter() and gives you a new counter. Each counter owns one count box. Call the counters in any order and watch which box changes.

Make two counters, call a() three times, then call b() once. You get 1 for b, because b has its own count. Then call a() again and it says 4. Here is the same thing in plain code:

const a = makeCounter();
const b = makeCounter();
console.log(a(), a(), a());   // 1 2 3
console.log(b());             // 1
console.log(a());             // 4

A useful trick: private variables

Because only the inner functions can reach count, nothing outside can change it by accident. That gives you a way to hide data in JavaScript:

function makeWallet(start) {
  let balance = start;
  return {
    add(n) { balance += n; return balance; },
    show() { return balance; }
  };
}

const w = makeWallet(10);
w.add(5);
console.log(w.show(), w.balance);   // 15 undefined

The wallet object has two functions, and both share the one balance variable. Reading w.balance gives undefined, because balance is not a property of the object. It only lives in the closure.

The famous surprise: timers in a loop

Because a closure links to the variable and not to a copy of its value, you can get a result that confuses almost every beginner. This loop schedules three tiny timers (setTimeout with 0 means "run this function after the current code has finished"):

for (var i = 0; i < 3; i++) {
  setTimeout(function () { console.log("var", i); }, 0);
}
for (let j = 0; j < 3; j++) {
  setTimeout(function () { console.log("let", j); }, 0);
}

Real output from Node 22:

var 3
var 3
var 3
let 0
let 1
let 2

With var, there is one single i for the whole loop. The three timer functions all link to that same variable. By the time the timers run, the loop has finished and i is 3, so all three print 3.

With let, JavaScript makes a fresh variable for every pass of the loop, as the language specification defines it. Each timer function links to its own j, which still holds 0, 1 or 2. Same idea as the counters above: separate boxes, separate values.

Before let existed (it arrived in 2015), people fixed the var loop by calling a small function on every pass, so each call got its own variable:

for (var i = 0; i < 3; i++) {
  (function (copy) {
    setTimeout(function () { console.log("fixed", copy); }, 0);
  })(i);
}
// fixed 0, fixed 1, fixed 2

You may meet this pattern in older code. In new code, let does the job.

What to take away

A function remembers the variables around the place where it was written, and it keeps them alive for as long as it can still be called. Every call of the outer function creates a fresh set of those variables. The link is to the variable itself, so if the variable changes, the function sees the change.

When something in your code remembers a value you did not expect, ask: which function created this variable, and which functions still hold a link to it? Two counters that each have their own count and three timers that share one i are the same mechanism, seen with different boxes.

$ ls languages/

see all →
languages/ruby-blocks-how-a-method-borrows-a-piece-of-your-code-and-runs-it-with.md Ruby blocks: how a method borrows a piece of your code and runs it with yield Ruby code is full of curly-brace chunks like `list.each { |x| ... }`. Who runs that chunk, and when? This article follows a block from the… 4 min read · 31 Oct languages/reference-counting-how-python-knows-when-to-throw-an-object-away.md Reference counting: how Python knows when to throw an object away You never tell Python to free memory, yet unused objects disappear. How does it know when one is finished with? CPython keeps a tally on… 5 min read · 28 Oct languages/generators-how-a-python-function-can-pause-in-the-middle-and-pick-up-w.md Generators: how a Python function can pause in the middle and pick up where it left off A generator is a Python function that can pause halfway, hand you a value, and carry on later from the same line with its variables intact… 4 min read · 25 Oct languages/javascript-s-event-loop-why-settimeout-0-runs-after-the-code-below-it.md JavaScript's event loop: why setTimeout(…, 0) runs after the code below it Why does `setTimeout(fn, 0)` run after the lines below it, and why does a promise callback beat it? JavaScript has one worker and two… 5 min read · 22 Oct languages/rust-ownership-why-let-b-a-makes-a-unusable.md Rust ownership: why let b = a makes a unusable Why does Rust refuse to compile `let b = a;` followed by a use of `a`? Because of ownership: every value has exactly one owner, and… 5 min read · 19 Oct languages/ruby-strings-why-changing-b-also-changes-a.md Ruby strings: why changing b also changes a In Ruby, `b = a` does not copy a string: it puts a second label on the same object. So `b << "s"` changes `a` too, while `b += "s"` does… 4 min read · 16 Oct