CodingNic

Functions

Closures

Functions 20 min read

Closures

Objectives

By the end of this chapter, you should be able to:

  • Explain, in your own words, what a closure is
  • Trace through a closure example by hand and predict its output
  • Explain why two closures created from the same function stay independent of each other

💡 Why this matters: Closures show up constantly in real JavaScript, in code that hides internal state, in code that configures a function ahead of time, and (later in this course) in code that reacts to clicks and events. They also trip up almost every beginner at first, so we’re going to go slowly and prove every claim with running code.

What Is a Closure?

A closure is a function that remembers the variables from the place it was created, even after the outer code that created it has already finished running.

That definition is abstract, so let’s make it concrete with the smallest example that shows it clearly: a counter.

javascript
function makeCounter() {
  let count = 0;

  function increment() {
    count = count + 1;
    return count;
  }

  return increment;
}

Walk through what happens step by step:

  1. makeCounter runs. It creates a local variable count set to 0.
  2. Inside makeCounter, it defines a function increment, which reads and updates count.
  3. makeCounter returns increment and then finishes running.

Normally, once a function finishes, its local variables are gone. But increment still refers to count, so JavaScript keeps count alive, attached to increment, for as long as increment might still be called. That attachment, a function plus the variables it remembers, is the closure.

Proving It Actually Remembers

Let’s call makeCounter and use the function it returns, several times in a row:

javascript
function makeCounter() {
  let count = 0;

  function increment() {
    count = count + 1;
    return count;
  }

  return increment;
}

const counter1 = makeCounter();

console.log(counter1());
// 1

console.log(counter1());
// 2

console.log(counter1());
// 3

Each call to counter1() runs increment again. count isn’t reset to 0 between calls, because counter1 still holds the closure over the exact same count variable that was created back when makeCounter() ran once. That’s the “remembers a variable after the outer function has finished” part, proven: makeCounter finished running before we ever called counter1() the first time, yet the value kept growing across calls.

Each Closure Gets Its Own Copy

Now call makeCounter() a second time, creating a brand new counter:

javascript
function makeCounter() {
  let count = 0;

  function increment() {
    count = count + 1;
    return count;
  }

  return increment;
}

const counter1 = makeCounter();
console.log(counter1());
// 1

const counter2 = makeCounter();
console.log(counter2());
// 1

counter2 starts back at 1, completely unaffected by everything counter1 has done. Every call to makeCounter() creates a fresh count variable, and the closure returned each time is tied only to its own copy. counter1 and counter2 never interfere with each other.

A Second Example: Remembering a Parameter

Closures don’t just remember variables declared with let or const inside the outer function, they also remember parameters.

javascript
function makeGreeter(name) {
  return function () {
    console.log("Hello, " + name);
  };
}

const greetErin = makeGreeter("Erin");
const greetJordan = makeGreeter("Jordan");

greetErin();
// Hello, Erin

greetJordan();
// Hello, Jordan

greetErin and greetJordan are two different closures, each remembering the name it was created with. Calling greetErin() never looks at "Jordan", and calling greetJordan() never looks at "Erin", because each function has its own remembered copy of name from when makeGreeter was called.

Try It

  1. Trace makeCounter by hand: if you called counter1() four times in a row, what would the four logged values be? Then run it and check.
  2. Create two counters, counterA and counterB, from makeCounter. Call counterA() twice and counterB() once, in that order, logging each result. Predict the three values before you run it.
  3. Write your own makeGreeter-style function called makeFarewell(name) that returns a function logging "Goodbye, " followed by name. Create two farewells for different people and call both.

Recap

  • A closure is a function that remembers the variables from where it was created, even after the outer function has finished running.
  • Calling the outer function again creates a brand new, independent set of remembered variables, closures don’t share state with each other.
  • Closures can remember both local variables and parameters from the outer function.

Next lesson: recursion, a function that solves a problem by calling itself on a smaller version of that same problem.