AlgoInsight

Free lesson · JavaScript Basic to Advance

Closures Explained

Easy5 min read

What Is a Closure?

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

That sentence is the whole definition. Everything else in this Topic is explaining what it means and why it works. Memorize that sentence. When an interviewer asks "what is a closure?", that's the answer to give.

function makeGreeter(name) {
  return function() {
    console.log(`Hello, ${name}`);
  };
}

const greetOlena = makeGreeter("Olena");
const greetNikhil = makeGreeter("Nikhil");

greetOlena();
greetNikhil();

Two closures. Each one is a function that "remembers" its own name. greetOlena remembers "Olena". greetNikhil remembers "Nikhil". The function makeGreeter returned long ago — its execution context is no longer on the call stack — but the variables it created are still alive because the returned functions still reference them.

This is the mechanism we set up in Topic 6.1 and 6.2. Lexical scope means a function remembers where it was written. Execution contexts get cleaned up when functions return — unless something still holds a reference. The combination of those two facts is what produces closures.

Interactive animation: The mechanism drawn - the lookup walks into a call that already returned, and its variables are still there.

Why Closures Exist

Closures aren't a feature someone added to JavaScript. They're a consequence of how the language was designed.

JavaScript was designed so functions are first-class values (Topic 5.7). You can pass them around, return them from other functions, store them in variables. JavaScript was also designed with lexical scope — functions remember where they were defined. These two design decisions, combined, force closures into existence.

If a function can be returned from another function (first-class), and the returned function remembers its original scope (lexical scope), then that scope has to stay alive as long as the returned function does. There's no other way. Closures are the natural result.

Without closures, JavaScript couldn't have practical callbacks, couldn't have private state in functions, couldn't have most of the patterns that make modern JavaScript work the way it does. They're not optional. They're load-bearing.

Interactive animation: Kept by reachability - the captured scope stays alive for as long as the returned function does.

The Core Mental Model

A closure is a function bundled with a reference to the place it was created. When the function runs later, it looks up variables in that original place — not where it's being called from.

graph TD
    A[outer function runs] --> B[creates variable: count = 0]
    B --> C[creates inner function]
    C --> D[returns inner function]
    D --> E[outer function exits]
    E --> F[count is NOT garbage collected]
    F --> G[because inner function still holds a reference to it]
    G --> H[inner function runs later, still sees count]

    style F fill:#7c3aed,color:#fff
    style G fill:#7c3aed,color:#fff
This diagram shows the lifecycle of a closure. An outer function runs and creates a variable called count. Inside that outer function, an inner function is created that references count. The outer function returns the inner function and then exits. Normally the count variable would be cleaned up when the outer function exits, but because the inner function still holds a reference to count, count survives. When the inner function runs later, it can still see and modify count.

The crucial detail: the inner function doesn't copy the variables it captures. It holds a reference to them. If the variables change after the closure is created, the closure sees the new values:

function makeReporter() {
  let count = 0;

  return function() {
    console.log(`Count is now ${count}`);
  };
}

const report = makeReporter();
report();   // "Count is now 0"

// But what if count could change before report runs?

A more revealing version, where the variable does change:

function makeCounterAndReporter() {
  let count = 0;

  const increment = () => count = count + 1;
  const report = () => console.log(`Count is now ${count}`);

  return { increment, report };
}

const { increment, report } = makeCounterAndReporter();

report();      // "Count is now 0"
increment();
increment();
report();      // "Count is now 2"
increment();
report();      // "Count is now 3"

Both increment and report close over the same count. They share it. Changing it through one is visible through the other. This is closures used as shared private state.

Interactive animation: The capture is live - a closure holds a reference to the variable, so it sees later changes.

Worked Examples

Three examples building from simplest to most realistic.

Example 1 — The basic closure:

function makeMultiplier(factor) {
  return function(n) {
    return n * factor;
  };
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

console.log(double(5));    // 10
console.log(triple(5));    // 15
console.log(double(100));  // 200

makeMultiplier is called twice, producing two separate closures. Each one remembers its own factor. After makeMultiplier returns, the two factor variables continue to exist — one for each closure.

Example 2 — Closure that holds mutable state:

function makeCounter() {
  let count = 0;

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

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

The count variable lives inside makeCounter. After makeCounter returns, count is not accessible from anywhere except the returned function. There is no way to read count from outside. There is no way to overwrite it. It's preserved, and it's also private.

This is the foundational pattern for closures used as private state — a pattern you'll see in real-world code constantly.

Example 3 — Closures in code you've already written:

const buttons = ["save", "cancel", "delete"];

const handlers = buttons.map(function(buttonName) {
  return function() {
    console.log(`Clicked: ${buttonName}`);
  };
});

handlers[0]();
handlers[1]();
handlers[2]();

Three closures, created in a map callback. Each remembers its own buttonName. The variable buttonName doesn't exist after map finishes iterating — but each handler still has its own version of it, captured at the moment that handler was created.

This is closures in everyday code. Every time you write an event handler, an array callback, a setTimeout, you're creating a closure. The mechanism is invisible because it just works. But it's there underneath.

Interactive animation: Every factory call captures a fresh scope - two closures from one factory never share their state.

Closures Are Everywhere

Once you can recognize closures, you start seeing them in nearly every piece of modern JavaScript code:

// 1. Event handlers
function attachClickHandler(elementId, message) {
  // Pretend this is a DOM element. The handler closes over 'message'.
  const handler = () => console.log(message);
  // element.addEventListener("click", handler);
  return handler;
}

// 2. setTimeout callbacks
function delayedGreeting(name, delayMs) {
  setTimeout(() => {
    console.log(`Hello, ${name}`);   // closes over 'name'
  }, delayMs);
}

// 3. Array method callbacks
const numbers = [1, 2, 3];
const threshold = 2;
const above = numbers.filter(n => n > threshold);   // closes over 'threshold'
console.log(above);

// 4. Promises
function fetchUser(id) {
  return Promise.resolve({ id, name: "Olena" }).then(user => {
    return `User ${id}: ${user.name}`;   // closes over 'id'
  });
}

fetchUser(42).then(result => console.log(result));

Each of these is a closure. The pattern is consistent: a function created in one place gets called somewhere else, and it remembers values from where it was created. Most JavaScript developers use closures every day without thinking about them. Understanding them at the level of "what is actually happening here" is what separates senior JavaScript developers from intermediates.

Common Pitfalls

Warning: The closure traps.

Thinking closures copy values. They don't. A closure holds a reference to a variable. If the variable changes after the closure is created, the closure sees the new value. The famous version of this bug is the for loop with var — covered in Topic 6.6.

Memory leaks from forgotten closures. Because a closure keeps its captured variables alive, a closure attached to something long-lived (a DOM element, an event listener that's never removed, a global) will keep those variables alive too. If those variables reference large objects, you have a leak. Topic 6.6 covers this.

Confusing closure with scope. Scope is a rule about where a variable can be accessed. A closure is the thing — the bundle of function plus its remembered scope — that exists at runtime. Scope is the rulebook. Closure is what the rulebook produces. Mixing these up in an interview signals you've memorized the concept without understanding it.

Why This Matters in Interviews

Interview: Closures are the single most-tested JavaScript concept in interviews. Almost every JS-focused interview asks about them in some form — either directly ("explain closures") or indirectly ("why does this code log this value?").

The interviewer is testing whether you understand JavaScript's runtime model. A candidate who can recite a textbook definition but can't explain why the value persists has memorized rather than understood. A candidate who can walk through a closure execution step by step, identifying which scope each variable comes from, has signal that they understand JavaScript deeply.

The full Interview Lens Topic for closures (Topic 6.7) covers the question variants, the wrong answers interviewers hear most often, the three-layer answer structure, and the follow-ups that come after.

Try It

Exercise: Predict the output, then run it to check.

function makeMultiplier(factor) {
  return function(n) {
    return n * factor;
  };
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

console.log(double(5));
console.log(triple(5));
console.log(double(triple(2)));

Before you run it: what will each line print? Why? Which variable does each returned function remember, and from when?

Once you've predicted, run the code and check. If your prediction was right, you've got the model. If it wasn't, walk through the example again with the diagram in mind. Closures are not deep magic — they're just functions that remember their scope.

Back to JavaScript Basic to Advance