DEV Community

Cover image for JavaScript Closures Explained Simply (with Real Examples)
Gaurav Aggarwal
Gaurav Aggarwal

Posted on

JavaScript Closures Explained Simply (with Real Examples)

Ever wondered… How functions remember things… even after they've “forgotten” them.

🚀 Ever Seen JavaScript Do Magic?

Imagine if a function could remember everything it once knew — even after it’s long gone.
Sounds like wizardry? 🔮

Well, that’s not magic — that’s JavaScript Closures.

Closures are one of the most powerful, underestimated, and misunderstood features in JavaScript. Whether you’re building UI components, managing state, or writing reusable utilities — closures are quietly doing a lot of the heavy lifting.

📦 What is a Closure?

In JavaScript, a closure is created when a function “remembers” the variables from its lexical scope, even when that function is executed outside of that scope.

Example:

function outer() {
  let count = 0;

  function inner() {
    count++;
    console.log(`Count is ${count}`);
  }

  return inner;
}

const counter = outer(); // outer() runs, returns inner()
counter(); // Count is 1
counter(); // Count is 2
Enter fullscreen mode Exit fullscreen mode

Even though outer() has finished executing, the count variable is still accessible to inner() because of closure.

🧠 Why Are Closures Important?

Closures enable:

  • Data encapsulation and privacy (like private variables)
  • Callbacks with preserved state
  • Partial application / currying
  • Creating factories or modules
  • Maintaining state without using global variables

🧪 Real-World Examples of Closures

✅ 1. Private Variables (Encapsulation)

function createBankAccount() {
  let balance = 1000;

  return {
    deposit: (amount) => {
      balance += amount;
      return balance;
    },
    withdraw: (amount) => {
      if (amount > balance) return "Insufficient funds";
      balance -= amount;
      return balance;
    },
    checkBalance: () => balance
  };
}

const account = createBankAccount();
console.log(account.deposit(500));    // 1500
console.log(account.withdraw(200));   // 1300
console.log(account.checkBalance());  // 1300
console.log(account.balance);         // undefined
Enter fullscreen mode Exit fullscreen mode

Why it works: The balance variable is private, protected by a closure.

✅ 2. Function Factories (Like React Hooks or Custom Utilities)

function createGreeter(name) {
  return function (greeting) {
    console.log(`${greeting}, ${name}!`);
  };
}

const greetJohn = createGreeter("John");
greetJohn("Hello"); // Hello, John!
greetJohn("Good morning"); // Good morning, John!
Enter fullscreen mode Exit fullscreen mode

This is essentially how React’s useState or useEffect hooks maintain internal values per component instance.

✅ 3. Currying / Partial Application

function multiply(x) {
  return function (y) {
    return x * y;
  };
}

const double = multiply(2);
console.log(double(5)); // 10

const triple = multiply(3);
console.log(triple(4)); // 12
Enter fullscreen mode Exit fullscreen mode

Closures enable you to “store” the first parameter for future use.

✅ 4. Timers or Async Logic Holding State

function startCountdown(start) {
  let count = start;

  const intervalId = setInterval(() => {
    console.log(count);
    count--;

    if (count < 0) clearInterval(intervalId);
  }, 1000);
}

startCountdown(5);
Enter fullscreen mode Exit fullscreen mode

The callback in setInterval closes over count, so it can update and access it every second.

✅ 5. Loop + Closure Bug Fix

// Common mistake
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// Output: 3, 3, 3

// Fix using closure (or let)
for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(() => console.log(j), 1000);
  })(i);
}
// Output: 0, 1, 2
Enter fullscreen mode Exit fullscreen mode

Using a closure, we “capture” the value of i at each iteration.

🚧 Common Mistakes When Using Closures

  1. Memory Leaks:
    Keeping too many closures alive unnecessarily can retain memory.

  2. Unexpected Shared State:
    If you're using closures inside loops or factories, shared variables can behave unexpectedly.

🧩 Visualizing Closure

outer()
 └─ inner() ← retains access to `count` from outer scope
        ↑
   Closure keeps variables alive
Enter fullscreen mode Exit fullscreen mode

🎯 Closure in One Line

"A closure is when a function remembers its lexical environment — even after that environment is gone."

✨ Conclusion: Closures Are Everywhere

From React Hooks to debounce functions, from callback logic to async APIs — closures are the glue holding it all together.

They may feel like black magic at first, but once you understand them, you’ll see them in everything — and write cleaner, more powerful code because of it.

✍️ Want to Try This Yourself?

Here are a few prompts for readers:

  • Build a once() function — it should only allow a callback to be called once.
  • Create a timer with start/pause/reset using closure.
  • Create a simple quiz app where questions retain their state using closures.

“Understanding closures doesn’t just make you a better JavaScript developer — it makes you a smarter problem solver.” 💡


🙌 Found This Helpful?

If you enjoyed this read:

  • Clap 👏 to support the post
  • Bookmark 📌 for future reference
  • Share it with a dev buddy who’s still afraid of closures 😄

Have questions or want a follow-up post on secure local storage using closures and encryption? Drop it in the comments below.

Happy Coding!!😊

Top comments (0)