DEV Community

Cover image for JavaScript Closures Explained Simply
Kathirvel S
Kathirvel S

Posted on

JavaScript Closures Explained Simply

Let’s be honest.

Closures in JavaScript sound scary at first.
You’ve probably seen definitions like:

“A closure is a function that retains access to its lexical scope…”

And your brain immediately goes: Nope.

But here’s the truth — closures are not complicated. You’re already using them. You just don’t realize it yet.

So let’s break it down slowly, clearly, and in a way that actually sticks.


First — don’t scroll

Look at this code.

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

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

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

What do you think the output is?

Don’t skip. Think.

.

.

.

Now imagine you bet money on your answer.

Output:

Alex
Enter fullscreen mode Exit fullscreen mode

If that surprised you even a little… good. You’re in the right place.


Now here’s the real question

Why is name still available?

outer() is already done. Gone. Finished.

So why didn’t name disappear with it?

If your answer is:

“Because JavaScript is weird”

No. That’s just avoiding the truth.

Or worse — memorizing without understanding.


This is where closures hit you

A closure means:

A function remembers variables from where it was created — not where it runs.

Read that again.

Not where it runs.
Where it was created.

If that still feels abstract, don’t worry — I’m going to corner you with examples.


Still not convinced? Let’s push you

Real scenario: Login system

function createLogin(username, password) {
  return function (inputPassword) {
    if (inputPassword === password) {
      console.log(`Welcome ${username}`);
    } else {
      console.log("Wrong password");
    }
  };
}

const login = createLogin("Sam", "pass123");

login("pass123");
login("123");
Enter fullscreen mode Exit fullscreen mode

Output:

Welcome Sam
Wrong password
Enter fullscreen mode Exit fullscreen mode

Now answer this:

Where is password stored?

  • Not global
  • Not inside login
  • Not accessible anywhere

Yet it still works.

And no, it’s not “saved somewhere magically.”

That’s closure.

And if that didn’t click yet, don’t worry — I’m not done with you.


Let’s corner you a bit

Try this:

console.log(login.password);
Enter fullscreen mode Exit fullscreen mode

Before you run it — predict it.

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

So…

  • You can’t access it
  • But the function can

Sounds like magic?

It’s not magic. It’s memory + scope working together.


Real-world situation: You click a button

Let’s say you're tracking clicks.

function createClickTracker() {
  let count = 0;

  return function () {
    count++;
    console.log(`Clicked ${count} times`);
  };
}

const click = createClickTracker();

click();
click();
click();
Enter fullscreen mode Exit fullscreen mode

Output:

Clicked 1 times
Clicked 2 times
Clicked 3 times
Enter fullscreen mode Exit fullscreen mode

Now don’t just read.

Answer this:

Why is count not resetting to 0?

Pause. Actually think.

Because you're not creating count again.
You're reusing the same closed-over variable.


Let’s make it uncomfortable

What if I do this:

const click1 = createClickTracker();
const click2 = createClickTracker();

click1();
click1();

click2();
Enter fullscreen mode Exit fullscreen mode

Before you scroll — predict it.

Output:

Clicked 1 times
Clicked 2 times
Clicked 1 times
Enter fullscreen mode Exit fullscreen mode

Now think carefully.

If closures were “shared memory”… this wouldn’t happen.

Each function has its own private world.

Yes — like separate tabs in your browser.


Real app logic: Shopping cart

function createCart() {
  let items = [];

  return {
    add(item) {
      items.push(item);
      console.log(`${item} added`);
    },
    view() {
      console.log(items);
    }
  };
}

const cart = createCart();

cart.add("Laptop");
cart.add("Phone");
cart.view();
Enter fullscreen mode Exit fullscreen mode

Output:

Laptop added
Phone added
["Laptop", "Phone"]
Enter fullscreen mode Exit fullscreen mode

Now I dare you:

console.log(cart.items);
Enter fullscreen mode Exit fullscreen mode

Before running it — what do you expect?

Output:

undefined
Enter fullscreen mode Exit fullscreen mode

So let me ask you:

If you can’t access items, how is it still updating?

Because it lives inside a closure.

Not global. Not exposed. Not reachable.

Hidden — but alive.


Now let’s catch a bug (you’ve probably made this)

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

What do you expect?

Be honest.

Order 1
Order 2
Order 3
Enter fullscreen mode Exit fullscreen mode

What actually happens?

Order 4
Order 4
Order 4
Enter fullscreen mode Exit fullscreen mode

Yeah. That bug.

Don’t say “I wouldn’t make that mistake.”
You either already have — or you will.


Fix it using closure

for (var i = 1; i <= 3; i++) {
  (function (order) {
    setTimeout(function () {
      console.log(`Order ${order}`);
    }, 1000);
  })(i);
}
Enter fullscreen mode Exit fullscreen mode

Output:

Order 1
Order 2
Order 3
Enter fullscreen mode Exit fullscreen mode

Each loop creates a new memory.

Same code pattern. Different outcome.

That’s the twist closures bring.


Or use modern JS (yes, this is easier)

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

Same correct output.

Because let creates a new scope every time.

Different keyword. Completely different behavior.


Pause. Don’t scroll yet.

If I ask you now:

What is a closure?

Don’t repeat a definition.

Say it like a developer.

Say it like you actually understand it.


The version you should remember

A closure is when a function carries its data with it, even after the original function is gone.

Not stored globally.
Not recreated.
Carried.


Quick self-check (no cheating)

What will this print?

function createDiscount(discount) {
  return function (price) {
    return price - price * discount;
  };
}

const discount10 = createDiscount(0.1);

console.log(discount10(200));
Enter fullscreen mode Exit fullscreen mode

Don’t rush. Think.

.

.

.

Output:

180
Enter fullscreen mode Exit fullscreen mode

If you got that right, you’re not guessing anymore.

If you didn’t — good. That means you’re actually learning instead of memorizing.


Final reality check

Closures are not some “advanced topic.”

They are used in:

  • React hooks
  • Event listeners
  • API handlers
  • Timers
  • State management

So if you skip this…

You’re not skipping theory.
You’re skipping how JavaScript actually works.


One last push

Next time you see:

  • Function inside function
  • Returned function
  • Hidden variables
  • Values that “don’t reset”

Don’t ignore it.

Stop and ask:

“What is this function remembering?”

That question alone will level you up.

And now you actually understand closures — not just pretend to.

Top comments (0)