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();

What do you think the output is?

Don’t skip. Think.

.

.

.

Now imagine you bet money on your answer.

Output:

Alex

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");

Output:

Welcome Sam
Wrong password

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);

Before you run it — predict it.

Output:

undefined

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();

Output:

Clicked 1 times
Clicked 2 times
Clicked 3 times

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();

Before you scroll — predict it.

Output:

Clicked 1 times
Clicked 2 times
Clicked 1 times

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();

Output:

Laptop added
Phone added
["Laptop", "Phone"]

Now I dare you:

console.log(cart.items);

Before running it — what do you expect?

Output:

undefined

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);
}

What do you expect?

Be honest.

Order 1
Order 2
Order 3

What actually happens?

Order 4
Order 4
Order 4

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);
}

Output:

Order 1
Order 2
Order 3

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);
}

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));

Don’t rush. Think.

.

.

.

Output:

180

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.