A Minimal Example

counter.js function makeCounter() { let count = 0; // private variable return function () { count++; // inner function "closes over" count return count; }; } const counter = makeCounter(); console.log(counter()); // 1 console.log(counter()); // 2 console.log(counter()); // 3

makeCounter() runs and returns. Normally, count would be garbage collected once the function exits. But because the returned inner function still references count, JavaScript keeps it alive. Every call to counter() shares the same remembered count variable.

Why This Happens: Lexical Scope

JavaScript uses lexical scoping — a function's access to variables is determined by where it was written in the code, not where it is called from. When a function is defined inside another function, it keeps a live reference to the outer function's variables. That live reference is the closure.

The Classic var-in-a-loop Bug

broken.js — prints 3, 3, 3 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); }

var is function-scoped, not block-scoped, so all three callbacks share a single i variable. By the time the timeouts fire, the loop has already finished and i is 3.

fixed.js — prints 0, 1, 2 for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); }

let is block-scoped — each loop iteration gets its own fresh binding, so each closure captures a different i.

⚠️ This Is a Top Interview Question

"Why does this loop print the same number three times, and how do you fix it?" is one of the most commonly asked JavaScript interview questions — because it tests whether you actually understand closures, not just whether you can define the word.

Common Real-World Uses of Closures

Use CaseHow Closures Help
Private stateVariables in an outer function become inaccessible from outside, simulating private fields
Memoization / cachingAn inner function can read and update a cache object held in the outer scope
Event handlersA handler can remember data from when it was attached, without global variables
Currying / partial applicationA function returns another function pre-loaded with some arguments already "remembered"
Module patternAn IIFE returns an object whose methods close over private internal state

Private State Example

bank-account.js function createAccount(initialBalance) { let balance = initialBalance; // not accessible from outside return { deposit(amount) { balance += amount; return balance; }, withdraw(amount) { balance -= amount; return balance; }, getBalance() { return balance; } }; } const acc = createAccount(100); acc.deposit(50); console.log(acc.getBalance()); // 150 console.log(acc.balance); // undefined — truly private

Closures and Memory

Because a closure keeps its outer scope alive, anything referenced by a long-lived closure cannot be garbage collected. This is usually harmless, but it becomes a memory leak when a closure attached to a DOM element or event listener is never cleaned up — the closure (and everything it references) stays in memory for the lifetime of the page.

💡 Mental Model

Think of a closure as a backpack. Every function carries a backpack containing the variables it could see when it was created. The function can open that backpack and use those variables no matter where it is later called from.

How We Research and Update This Guide

We test the underlying formula or workflow, compare outputs with reliable references, and revise examples whenever the page content changes.

  • The workflow or formula is tested directly in the tool and compared against independent reference examples.
  • Examples are kept practical so readers can verify the result without hidden assumptions.
  • Pages are revised whenever the interface, calculation flow, or surrounding guidance materially changes.

Frequently Asked Questions — Closures