DEV Community

Anshul Sharma
Anshul Sharma

Posted on

JavaScript Closures Explained Like You're Learning Them for the First Time

JavaScript Closures Explained Like You're Learning Them for the First Time

Have You Ever Wondered How This Works?

Imagine you have a function:

function createCounter() {
  let count = 0;

  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();

counter();
counter();
counter();
Enter fullscreen mode Exit fullscreen mode

Output:

1
2
3
Enter fullscreen mode Exit fullscreen mode

At first glance this looks strange.

The variable count belongs to createCounter().

Once createCounter() finishes executing, shouldn't count disappear?

Then why does JavaScript still remember it?

The answer is: Closures.

But before understanding closures, let's understand how variables normally behave.


How Variables Normally Work

Consider this function:

function greet() {
  let name = "Anshul";

  console.log(name);
}

greet();
Enter fullscreen mode Exit fullscreen mode

Output:

Anshul
Enter fullscreen mode Exit fullscreen mode

After the function finishes:

greet();
Enter fullscreen mode Exit fullscreen mode

the variable:

name
Enter fullscreen mode Exit fullscreen mode

is removed from memory because it is no longer needed.

Think of it like this:

Function starts
    ↓
Variable created
    ↓
Function ends
    ↓
Variable removed
Enter fullscreen mode Exit fullscreen mode

This is normal JavaScript behavior.


Now Let's Break the Rules

Look at this code:

function outer() {
  let name = "Anshul";

  return function inner() {
    console.log(name);
  };
}

const sayHello = outer();

sayHello();
Enter fullscreen mode Exit fullscreen mode

Output:

Anshul
Enter fullscreen mode Exit fullscreen mode

Something interesting happened.

The function outer() finished executing.

Yet inner() can still access:

name
Enter fullscreen mode Exit fullscreen mode

How?


What JavaScript Actually Does

When JavaScript creates inner(), it notices that the function uses a variable from its parent scope:

name
Enter fullscreen mode Exit fullscreen mode

Instead of deleting that variable, JavaScript keeps it alive.

It creates a hidden connection between:

inner()
      +
remembered variables
Enter fullscreen mode Exit fullscreen mode

This connection is called a Closure.


The Simplest Definition

A closure is:

A function that remembers variables from its outer scope even after the outer function has finished executing.

That's it.

No complicated terminology required.


Visualizing a Closure

Step 1:

const sayHello = outer();
Enter fullscreen mode Exit fullscreen mode

Memory:

outer()

name = "Anshul"
Enter fullscreen mode Exit fullscreen mode

Step 2:

return inner;
Enter fullscreen mode Exit fullscreen mode

JavaScript stores:

inner()
   |
   └── remembers name = "Anshul"
Enter fullscreen mode Exit fullscreen mode

Step 3:

sayHello();
Enter fullscreen mode Exit fullscreen mode

Output:

Anshul
Enter fullscreen mode Exit fullscreen mode

Even though outer() no longer exists.


Real-World Example #1: Counter

Let's build a counter.

function createCounter() {
  let count = 0;

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

const counter = createCounter();

console.log(counter());
console.log(counter());
console.log(counter());
Enter fullscreen mode Exit fullscreen mode

Output:

1
2
3
Enter fullscreen mode Exit fullscreen mode

Why?

Because the returned function remembers:

count
Enter fullscreen mode Exit fullscreen mode

between executions.

Memory looks like:

counter()
      |
      └── count = 3
Enter fullscreen mode Exit fullscreen mode

The value survives because of the closure.


Real-World Example #2: Private Variables

Suppose we're building a bank account.

We don't want anyone changing the balance directly.

Bad:

account.balance = 1000000;
Enter fullscreen mode Exit fullscreen mode

Let's hide it.

function createAccount() {
  let balance = 1000;

  return {
    deposit(amount) {
      balance += amount;
    },

    getBalance() {
      return balance;
    }
  };
}

const account = createAccount();

account.deposit(500);

console.log(account.getBalance());
Enter fullscreen mode Exit fullscreen mode

Output:

1500
Enter fullscreen mode Exit fullscreen mode

Trying this:

console.log(account.balance);
Enter fullscreen mode Exit fullscreen mode

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

The variable is protected inside the closure.


Real-World Example #3: Function Factory

Closures allow us to generate customized functions.

function multiplyBy(multiplier) {
  return function (number) {
    return number * multiplier;
  };
}

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

console.log(double(5));
console.log(triple(5));
Enter fullscreen mode Exit fullscreen mode

Output:

10
15
Enter fullscreen mode Exit fullscreen mode

Each function remembers its own value.

double()
   remembers multiplier = 2

triple()
   remembers multiplier = 3
Enter fullscreen mode Exit fullscreen mode

Why Closures Are Important

Closures are used everywhere in JavaScript:

  • Event handlers
  • React Hooks
  • Debouncing
  • Throttling
  • Authentication systems
  • State management
  • Timers
  • Function factories

Even if you don't write closures intentionally, you're already using them.


Common Interview Question

What will this print?

function outer() {
  let x = 10;

  return function () {
    console.log(x);
  };
}

const fn = outer();

fn();
Enter fullscreen mode Exit fullscreen mode

Answer:

10
Enter fullscreen mode Exit fullscreen mode

Because the returned function remembers x.


Common Mistake

Consider:

for (var i = 1; i <= 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
Enter fullscreen mode Exit fullscreen mode

Output:

4
4
4
Enter fullscreen mode Exit fullscreen mode

Why?

Because all callbacks share the same variable.

Using let:

for (let i = 1; i <= 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
Enter fullscreen mode Exit fullscreen mode

Output:

1
2
3
Enter fullscreen mode Exit fullscreen mode

Each iteration gets its own closure.


Are Closures Bad for Memory?

Not at all.

But if a closure keeps referencing a huge object, JavaScript cannot remove that object from memory.

Example:

function createHandler() {
  const hugeData = new Array(1000000).fill("data");

  return function () {
    console.log(hugeData.length);
  };
}
Enter fullscreen mode Exit fullscreen mode

Since the returned function uses hugeData, it stays in memory.

This can sometimes lead to memory issues if not handled carefully.


Final Thoughts

A closure is simply:

A function remembering variables from the place where it was created.

That's all.

Whenever a function accesses variables from an outer scope and keeps using them later, a closure is created.

Once you understand closures, concepts like:

  • Debouncing
  • Throttling
  • React Hooks
  • Memoization
  • State Management

become much easier to understand.

The next time someone says "closure," think:

Function + Remembered Variables = Closure

Top comments (0)