Skip to main content

Command Palette

Search for a command to run...

Closures in JavaScript

How Functions Remember Things

Updated
Closures in JavaScript

In the previous blog, we understood how JavaScript executes code behind the scenes using concepts like hoisting, creation phase, and execution phase.Now we move into something that feels almost like magic when you first see it.

Closures.

Closures are one of those concepts that seem simple when explained, but take time to truly understand. And once you do understand them, a lot of advanced JavaScript suddenly starts making sense.


Why Closures Matter

Let’s start with a simple question. When a function finishes executing, what happens to its variables?

Most people would say they are destroyed. And in most cases, that is true.

But not always.


A Strange Example

function outer() {
  let count = 0;
  function inner() {
    count++;
    console.log(count);
  }
  return inner;
}
const counter = outer();

counter(); // 1
counter(); // 2
counter(); // 3

Now pause and think.

The function outer() has already finished executing. So how is count still increasing? Why is it not reset to 0 every time? This behavior is what closures explain.


What is a Closure?

A closure is created when a function remembers variables from its outer scope even after that outer function has finished executing.

In simple terms:

A function + its surrounding state = closure

Breaking Down the Example

function outer() {
  let count = 0;
  function inner() {
    count++;
    console.log(count);
  }
  return inner;
}

When outer() runs:

  • count is created

  • inner is defined

  • inner is returned

Now here is the key part. When inner is returned, it does not just return the function. It returns the function along with access to count.

That combination is the closure.

Mental Model

Think of it like this:

  • The inner function carries a backpack

  • Inside the backpack are all the variables it needs

  • Even if the outer function is gone, the backpack stays

So inner keeps access to count.

Important Point

  1. Closures do not store values.

  2. They store references to variables.

  3. That is why the value updates.


Another Example

function createMultiplier(multiplier) {
  return function(num) {
    return num * multiplier;
  };
}
const double = createMultiplier(2);
const triple = createMultiplier(3);

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

Each function remembers its own multiplier. This is closure in action.


Closures and Lexical Scope

Closures are based on something called lexical scope. Lexical scope means: A function can access variables defined in its outer scope.

function outer() {
  let name = "Shikhar";
  function inner() {
    console.log(name);
  }
  inner();
}

The inner function can access name. Closures take this one step further. They preserve this access even after the outer function is done.


Closures with Loops

for (var i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

Output:

4
4
4

Why? Because var is function scoped. All functions share the same i.

Fix with let

for (let i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

Output:

1
2
3

Because let creates a new scope for each iteration.

Fix with Closure

for (var i = 1; i <= 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 1000);
  })(i);
}

Each iteration creates a new closure with its own j.


Data Privacy Using Closures

Closures allow you to create private variables.

function createCounter() {
  let count = 0;

  return {
    increment: function() {
      count++;
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();

counter.increment();
counter.increment();

console.log(counter.getCount()); // 2

Here, count cannot be accessed directly. It is protected inside the closure.


Closures in Real Applications

Closures are used everywhere:

  • Event handlers

  • Callbacks

  • Timers

  • Modules

  • State management

Example:

function setupButton(button) {
  let clicks = 0;

  button.addEventListener("click", function() {
    clicks++;
    console.log(clicks);
  });
}

Each button remembers its own click count.


Common Mistakes

  • Thinking closures copy values

  • Not understanding reference behavior

  • Confusing scope with closure

  • Ignoring how loops interact with closures

Performance Consideration

Closures keep references alive. If not used properly, they can lead to memory issues. But in most cases, this is not something you need to worry about early on.


Putting It All Together

function createUser(name) {
  let loginCount = 0;

  return {
    login: function() {
      loginCount++;
      console.log(name + " logged in " + loginCount + " times");
    }
  };
}

const user = createUser("Shikhar");

user.login();
user.login();

This combines:

  • closures

  • private state

  • reusable logic


Final Thought

Closures are not a special feature. They are a natural result of how JavaScript handles scope and functions. But understanding them properly changes how you think about code. Instead of just writing functions, you start controlling data flow and state. And that is where JavaScript starts becoming powerful.