Archives

MY APPROACH

The right solution, not the biggest solution.

You don't need to know whether your problem needs an API, an automation workflow, a dashboard or a custom application.

Tell me the problem. I'll help figure out a practical way to solve it.

Closure Explained with a Counter: The JavaScript Interview Question Everyone Fails

Here is a JavaScript interview question that looks simple but exposes whether someone actually understands closures:

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

const counter = createCounter();
console.log(counter()); // ?
console.log(counter()); // ?
console.log(counter()); // ?

The output is:

1
2
3

Why does count not disappear after createCounter() finishes? The answer is closure.

What Are JavaScript Closures?

A closure is a function that remembers and can access variables from its surrounding lexical scope, even after the outer function has finished executing.

In the example, createCounter() creates the variable count and returns an inner function. That inner function keeps a reference to count.

When this line runs:

const counter = createCounter();

JavaScript does not simply return an isolated function with no context. The returned function retains access to the environment where it was created.

Each time counter() runs, it updates the same captured variable:

  • First call: count changes from 0 to 1.
  • Second call: count changes from 1 to 2.
  • Third call: count changes from 2 to 3.

JavaScript Closure and Lexical Scope

Closures work because JavaScript uses lexical scoping. A function can access variables based on where it was defined, not where it is called.

function createGreeting() {
  const message = "Hello";
  return function greet(name) {
    return `${message}, ${name}`;
  };
}
const greet = createGreeting();
console.log(greet("Rashid")); // Hello, Rashid

The greet() function still accesses message, even though createGreeting() has already returned.

This is the central idea behind a JavaScript closure: the function retains access to the variables it needs from its creation environment.

Each Counter Has Its Own Private State

Closures become more interesting when you create multiple counters:

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

const firstCounter = createCounter();
const secondCounter = createCounter();

console.log(firstCounter()); // 1
console.log(firstCounter()); // 2

console.log(secondCounter()); // 1
console.log(secondCounter()); // 2

Each call to createCounter() creates a separate lexical environment. Therefore, firstCounter and secondCounter do not share the same count.

This makes closures useful for creating simple forms of private variables without exposing the internal state directly.

Real-World Uses of JavaScript Closures

Closures are used in many ordinary JavaScript features and patterns:

  • Event handlers: Remembering configuration or state.
  • Timers: Accessing variables when a callback executes later.
  • Function factories: Creating customized functions.
  • Memoization: Caching previously calculated results.
  • Encapsulation: Keeping implementation details private.
function createMultiplier(multiplier) {
  return function (value) {
     return value * multiplier;
  };
}

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

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

Common Closure Gotchas

1. Closures and garbage collection

A closure does not automatically create a memory leak. Its captured variables remain available while the closure is still reachable—for example, when it is stored in an active event listener, timer, object, or application-level collection.

When the closure and everything referencing it become unreachable, JavaScript’s garbage collector can reclaim the closure and its captured environment, assuming no other references remain.

The practical risk is accidentally keeping a closure alive longer than necessary, especially when it captures large objects, DOM nodes, or other application data. Remove unused event listeners, clear unnecessary timers, and avoid storing short-lived closures in long-lived objects.

2. var can expose loop-related surprises

Closures created inside loops using var often share the same function-scoped variable. Using let generally creates a separate binding for each loop iteration.

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}
// 0
// 1
// 2

3. A closure is not a snapshot of every variable

A closure retains access to its lexical environment. If a captured variable changes, later calls can observe that changed value. Thinking of a closure as a frozen copy of the surrounding scope often leads to incorrect predictions.

Once you understand lexical scope and retained execution environments, the counter question is no longer a trick. It becomes a straightforward example of how JavaScript functions can carry state across calls—a behavior used everywhere from event-driven interfaces to reusable function factories.

 

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment

RDA
Rashid Anwar
Independent Developer

I’m Rashid, an independent developer focused on practical software solutions for businesses.

HAVE A PROJECT IN MIND?

Let's talk about the problem you're trying to solve.

Share what you need. We can discuss the requirements and a practical next step.

Start a Conversation