obah sylva

Understanding Closures in JavaScript (With Real Examples)

Share:fXinW
Share this article

A closure is one of those JavaScript concepts developers use correctly for years before they can actually explain it in an interview. The short definition is simple — a function that remembers the variables from where it was created, even after that outer function has finished running — but the real value only clicks once you see the everyday patterns that depend on it.

What a Closure Actually Is

Every function in JavaScript keeps a live reference to the variables in scope at the moment it was defined, not just the variables it needs at the moment it runs. That reference is the closure. It’s not a special syntax or a feature you opt into; it’s simply how JavaScript’s lexical scoping works, and it happens every time a function is defined inside another function.

function makeCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

count lives inside makeCounter, but the returned inner function keeps access to it long after makeCounter has finished executing. Each call to counter() reads and updates the same count variable, because the closure holds a live reference, not a snapshot.

Real Example: Private State Without Classes

Closures are the original way JavaScript achieved private variables, well before class and private fields existed. A variable trapped inside a closure simply can’t be reached from outside — there’s no dot-notation path to it.

function createBankAccount(initialBalance) {
  let balance = initialBalance;
  return {
    deposit(amount) { balance += amount; return balance; },
    withdraw(amount) {
      if (amount > balance) throw new Error('Insufficient funds');
      balance -= amount;
      return balance;
    },
    getBalance() { return balance; }
  };
}

const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
console.log(account.balance); // undefined — not accessible

Computer screen displaying JavaScript code representing closures

Real Example: The Classic Loop Bug

The most common place closures bite beginners is inside a loop with var. Because var is function-scoped rather than block-scoped, every closure created inside the loop shares the exact same variable, and all of them end up pointing at its final value.

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3 — not 0, 1, 2

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100);
}
// Logs: 0, 1, 2 — because let creates a new binding per iteration

Switching var to let fixes this because let creates a fresh binding for every loop iteration, so each closure captures its own separate variable instead of sharing one.

Real Example: Function Factories and Memoization

Closures also power function factories — functions that generate other, specialized functions — and memoization, where a closure caches expensive results between calls.

function memoize(fn) {
  const cache = new Map();
  return function (arg) {
    if (cache.has(arg)) return cache.get(arg);
    const result = fn(arg);
    cache.set(arg, result);
    return result;
  };
}

const slowSquare = (n) => { for (let i = 0; i < 1e6; i++); return n * n; };
const fastSquare = memoize(slowSquare);
fastSquare(5); // computed and cached
fastSquare(5); // returned instantly from cache

Watch Out: Closures and Memory

Because a closure keeps its outer variables alive, holding onto a closure longer than necessary can keep large objects in memory that would otherwise be garbage collected. This rarely matters for short-lived UI callbacks, but it’s worth attention in long-running Node.js processes or event listeners that are never removed — the closure keeps everything it references alive for as long as the function itself is reachable.

Frequently Asked Questions

Are closures unique to JavaScript?
No, closures exist in many languages with first-class functions, including Python, Swift, and Ruby. JavaScript just makes them unusually visible because so many everyday patterns — event handlers, callbacks, module patterns — depend on them.
Do closures cause memory leaks?
Not by themselves, but they can extend the lifetime of variables longer than expected if a closure is kept alive (for example, an event listener that’s never removed), since the closure holds a reference to everything in its outer scope.
Why does using let instead of var fix the loop closure bug?
var is function-scoped, so every iteration of a loop shares one single variable; let is block-scoped, so JavaScript creates a distinct binding for each iteration, and each closure captures its own separate copy.

Want more JavaScript deep dives like this? Explore more Web Development articles.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top