Find a subject or note

2 results found

JavaScriptCallBacks

How Functions call each other when JS remembers us

JavaScriptJavaScript Closures

How functions remember where they came from — and why it matters.

JavaScript Closures

You’ve probably used a closure today. In an event handler, a timer, or a callback that reaches for a variable outside itself. The name can sound intimidating; the idea is surprisingly familiar.

Let’s build a clear mental model, walk through a small example, and turn it into an answer you can actually use in an interview.

Start with the surrounding scope

Imagine a function that prepares a greeting for one person. It returns another function, ready to be called later:

greeting.js
function createGreeting(name) {
  const greeting = `Hello, ${name}!`;

  return function sayHello() {
    return greeting;
  };
}

const greetMaya = createGreeting('Maya');

console.log(greetMaya()); // "Hello, Maya!"
The outer call finishes. The returned function can still read greeting.

When we call greetMaya(), createGreeting has already finished. Yet sayHello can still read greeting. It was defined inside that scope, and it retains access to it.

This is lexical scope: where a function is written determines the surrounding variables it can access. Moving the function into another variable or calling it elsewhere doesn’t change that relationship.

A function and its environment

Think of the returned function and its surrounding environment as a pair. The environment supplies the variables that the function needs when it runs.

greetMaya points to the returned sayHello function. sayHello retains access to greeting, whose value is Hello, Maya!, in its surrounding lexical environment.
The reference to the surrounding environment is what makes the value accessible later.

This is not a special feature of returned functions. Functions form closures when they are created. Returning one simply makes the lifetime of that access easier to see.

Remembering a variable, not a snapshot

Here’s where interview questions become interesting. A closure keeps access to a binding. It doesn’t automatically freeze the value that binding held when the function was created.

counter.js
function createCounter() {
  let count = 0;

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

const next = createCounter();

console.log(next()); // 1
console.log(next()); // 2
console.log(next()); // 3

Every call to next() reaches the same count binding. The updated value is there the next time we call it. Calling createCounter() again creates a separate environment with its own counter.

What does this print?

Take a moment before opening the answer. There are two calls to the factory, but three calls to the returned functions.

A quick interview question
const first = createCounter();
const second = createCounter();

console.log(first());
console.log(first());
console.log(second());
Think it throughWhat are the three outputs, and why?

The output is:

1
2
1

first and second were created by different calls to createCounter. Each call created its own count binding. Calling first twice updates its counter to 2; second still begins at 0, so its first call returns 1.

A useful follow-up: if we instead wrote const second = first, both variables would refer to the same function and share the same counter.

The loop question

A familiar variation creates callbacks inside a loop. The important question is whether those callbacks share one changing binding or get a binding for each iteration.

Loop bindings at a glance

Inside a for loop

var i

let i

Binding

One shared binding

A new binding per iteration

Callbacks run after i < 3 finishes

3, 3, 3

0, 1, 2

Why?

Each callback reads the same final i

Each callback reads its iteration’s i

The timing matters here. The comparison assumes the callbacks run after the loop finishes, as they do with a timer. It doesn’t describe a callback invoked immediately during each iteration.

Explain it in an interview

Start with the concept, show a small example, and explain which variable the function can still access. You don’t need to begin with a long definition.

For a follow-up, talk about a real use: retaining configuration for an event handler, keeping internal state behind a small API, or explaining why a callback sees a particular variable.

One last detail: closures don’t automatically cause memory leaks. Retained references can keep data reachable, so release callbacks or subscriptions when you no longer need them.

Keep this with you

  • Look at where the function was created to find its surrounding scope.

  • Ask which binding it reads, and whether that binding changes.

  • Distinguish multiple calls to a factory from multiple calls to one returned function.

For a deeper reference, read the MDN guide to closures.

Thanks for reading this.

Back to JavaScript topics