Closures

A function that remembers the variables from where it was created, even after that outer code has finished running.

What is it?

Normally, when a function finishes running, the variables it created are thrown away — there's nothing left to hold onto them. But if that function creates another function inside it, and hands that inner function back out, something interesting happens: the inner function keeps access to the outer variables, even though the outer function has already finished.

This "memory" is called a closure. It's not a special feature you turn on — it happens automatically, any time a function is defined inside another function and used outside of it.

Explain like I'm 10

Imagine a box that remembers what you put inside it. You seal it up and hand it to a friend. Weeks later, they can still open the box and find exactly what was placed inside — even though you're long gone.

Examples

A simple closure

function makeCounter() {
  let count = 0;

  return function () {
    count = count + 1;
    return count;
  };
}

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

makeCounter finishes running immediately, but the inner function it returns still remembers and can update count every time it's called.

A private bank balance

function makeAccount(startingBalance) {
  let balance = startingBalance;

  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      balance -= amount;
      return balance;
    },
  };
}

const account = makeAccount(100);
account.deposit(50);   // 150
account.withdraw(30);  // 120
// there is no way to read or set "balance" directly from outside

This time the closure is shared by two returned functions instead of one, and balance is completely private — the only way to affect it is through deposit/withdraw.

How it works

When a function is created, it keeps a hidden link to the scope it was created in — not just the scope it's called from. So even after makeCounter returns, the inner function still has a live connection to the count variable, and can read and update it on every call.

Function created
       ↓
Variables available
       ↓
Function returned
       ↓
Function remembers variables
       ↓
Function called later

Why does it exist?

Closures let you create private state — data that only one function (or a small group of functions) can access and update, without exposing it as a global variable anyone could accidentally change. They're the foundation for patterns like counters, caches, and event handlers with private data.

When to use it

Reach for a closure whenever you want a piece of state that only one function (or a small group of related functions) can touch — a counter, a cache, a toggle, a configuration value set up once and reused on every call.

When not to use it

If the state genuinely needs to be shared, modified from many unrelated places, or inspected from outside, a closure's privacy works against you — a plain object or a class is usually clearer. And don't reach for a closure just to avoid passing one extra parameter.

Common mistakes

  • Creating closures inside a loop and expecting each one to capture a different value of the loop variable when using var (they all share the same one — let fixes this).

  • Thinking a closure copies the outer variable's value — it actually keeps a live reference, so if the value changes later, the closure sees the new value.

  • Overusing closures for state that would be simpler as a regular object or class.

Practice exercises

  1. Easy:

    Create a counter using a closure, similar to the example above, but that can also be reset back to 0.

  2. Medium:

    Create a private bank balance: a function that returns deposit and withdraw functions sharing one hidden balance variable.

  3. Hard:

    Build a reusable closure-based utility, once(fn), that only lets a given function run one time no matter how many times it's called.

Interview questions

What is a closure?

A function that keeps access to the variables from the scope it was defined in, even after that outer scope has finished executing — the function carries its birthplace with it wherever it's used.

Mechanically, how does a closure keep a variable alive after its outer function has returned?

When a function is created, it keeps an internal link to the lexical environment (the set of variable bindings) it was defined in. As long as any function still holds that link, the engine can't garbage-collect those variables, even though the function call that created them has long finished.

What does this log? `function makeCounter() { let count = 0; return () => ++count; } const counter = makeCounter(); console.log(counter()); console.log(counter());`

1 then 2 — each call to the returned function reads and updates the very same closed-over count variable, which persists across calls instead of resetting.

What does this log? `function makeCounter() { let count = 0; return () => ++count; } const a = makeCounter(); const b = makeCounter(); a(); a(); console.log(a(), b());`

3 1 — every call to makeCounter() creates a brand-new, independent count variable and a new closure over it, so a and b never share state even though they came from the same function.

What does this log? `function createFns() { const fns = []; for (var i = 0; i < 3; i++) { fns.push(() => console.log(i)); } return fns; } createFns().forEach((fn) => fn());`

3, 3, 3 — all three closures capture the same var i, which has finished looping and equals 3 by the time any of them actually runs.

How does swapping `var i` for `let i` change the previous example's output, and why?

It logs 0, 1, 2 instead — let gives each loop iteration its own fresh binding of i, so each pushed closure captures a distinct variable holding a distinct value, rather than all three sharing one.

Before `let` existed, how did developers fix the closure-in-a-loop problem with `var`?

By wrapping the loop body in an IIFE that took the current value as a parameter — (function (j) { fns.push(() => console.log(j)); })(i); — creating a new function scope, and therefore a new variable, on every iteration.

Does a closure capture a variable's value at the time it's created, or a live reference to the variable itself?

A live reference — the closure doesn't snapshot the value, it keeps a connection to the actual binding, so if that variable changes later, every closure over it sees the new value on its next read.

What does this log? `function makeGetter() { let value = "first"; const getter = () => value; value = "second"; return getter; } console.log(makeGetter()());`

"second" — the closure reads value live at call time, not at the moment it was defined, so the reassignment that happens before getter is even returned is fully visible.

How do closures enable private state?

A variable declared inside a function is only reachable from code with access to that function's scope. If the only way to read or change it is through functions returned from that scope (like deposit/withdraw), the variable is effectively private — nothing outside can reach it directly, unlike a property on a plain object.

What does this log? `function makeAccount() { let balance = 0; return { add: (n) => (balance += n), get: () => balance }; } const acc = makeAccount(); acc.add(5); acc.add(3); console.log(acc.get());`

8 — add and get are two different functions, but they both close over the exact same balance variable from the single call to makeAccount(), so changes made through one are visible through the other.

What is currying, and how do closures make it work?

Currying transforms a function that takes multiple arguments into a chain of functions that each take one argument and return the next function in the chain. Each returned function closes over the arguments already supplied, so by the time the last one runs, it has access to all of them.

What does this log? `const add = (a) => (b) => (c) => a + b + c; console.log(add(1)(2)(3));`

6 — each call returns a new function that closes over the argument just supplied; by the innermost call, closures over a, b, and c are all still reachable, so they can be added together.

How does memoization rely on closures?

A memoized function wraps the real computation together with a cache object captured in a closure. Every call checks that shared cache first — since the cache is a variable closed over by the returned function, it persists across calls instead of being recreated each time.

How do `debounce` and `throttle` use closures?

Both return a new function that closes over state that needs to persist between calls — typically a timer id or a 'last run' timestamp — so that each invocation of the wrapped function can check and update the same piece of state left behind by the previous call.

What does this log? `const obj = { name: "Amara", greetLater() { setTimeout(() => console.log(this.name), 0); } }; obj.greetLater();`

"Amara" — the arrow function has no this of its own, so it closes over this from greetLater's scope the same way it would close over any other variable; since greetLater was called as obj.greetLater(), its this is obj, and the arrow function inherits that.

What does this log, and how does it differ from the arrow-function version? `const obj = { name: "Amara", greetLater() { setTimeout(function () { console.log(this.name); }, 0); } }; obj.greetLater();`

undefined (or a TypeError in strict mode). A regular function gets its own this, determined by how it's called — setTimeout calls it as a plain function with no receiver, so this isn't obj here, unlike the arrow function, which has no this of its own to override.

Can closures cause memory leaks, and how?

Yes — as long as a closure is reachable (say, still registered as an event listener), every variable it closes over stays alive too, even if nothing else needs them. Holding onto a closure that references a large object (cached DOM nodes, big data) longer than necessary keeps that memory from being freed.

Do modern JS engines keep an entire outer scope alive just because one closure references it?

Not necessarily — many engines optimize by keeping alive only the specific variables actually referenced by the inner function, not the whole enclosing scope, though this is an implementation detail and shouldn't be relied on for correctness.

What's the difference between 'a function has its own scope' and 'a function is a closure'?

Every function has its own scope by default — that's just normal function scoping. It only becomes meaningfully a closure when the function is used after the scope that created it would otherwise have been destroyed, i.e. it outlives the call that defined it.

Why is the module pattern (an IIFE returning an object) considered an application of closures?

The IIFE's local variables become private state, and the object it returns exposes only the specific functions meant to be public — those functions close over the private variables, giving controlled access without ever exposing the variables themselves globally.

If two functions are returned from the same outer function call, do they share one closure or have two separate ones?

They share access to the same single lexical environment — there's one set of outer variables, and both returned functions hold a link to it, so a change either one makes to a shared variable is visible to the other.

How would you fix a closure that's holding onto more memory than it needs?

Avoid capturing more from the outer scope than the inner function actually uses, set references you no longer need to null once you're done with them, and remove event listeners or timers (and the closures they hold) when they're no longer needed.

Is it possible to create a closure without ever returning the inner function?

A closure technically forms any time a nested function is defined, but it's only observably useful once that inner function is called after, or independently of, the outer function's own execution — calling it purely from inside the outer function doesn't demonstrate anything a closure gives you that plain scope wouldn't.

How does the `once(fn)` pattern use a closure?

It returns a wrapper function that closes over a hidden flag (and often a cached result). The first call runs fn and flips the flag; every subsequent call checks that same closed-over flag and skips running fn again, returning the cached result instead.

What does this log? `function once(fn) { let called = false, result; return (...args) => { if (!called) { result = fn(...args); called = true; } return result; }; } const init = once(() => { console.log("running"); return 42; }); console.log(init()); console.log(init());`

"running" then 42, then just 42 again — the second call to init() finds called already true (from the shared closure) and skips re-running fn, returning the cached result instead.

Why can't code outside a closure directly read or set the variable it closes over?

The variable was never exposed as a global or as a property on any accessible object — it only exists inside the function scope where it was declared, and the only references to it are held internally by the closures created there.

What's the difference between a closure and simply passing a value as a function argument on every call?

A closure lets a function 'remember' state between separate calls without the caller having to keep re-supplying it — a parameter only lives for the duration of one call and has to be passed in again every time.

How would you use a closure to implement a simple in-memory cache for an expensive function?

Wrap the function in a closure holding a cache object (often keyed by JSON.stringify(args) or a Map); before computing, check whether the cache already has an entry for these arguments, and only call the real function and store the result if it doesn't.

Why does inspecting a closure in browser devtools show a 'Closure' section in the scope panel?

It's showing you exactly the outer variables that function actually references and has retained access to — a direct, visible confirmation of which bindings from an enclosing scope the closure is keeping alive.

Does a closure re-create the outer function's variables every time the inner function is called, or just once?

Once — the variables are created when the outer function runs and the closure forms at that point; every subsequent call to the inner function reads and writes that same, single set of variables rather than getting a fresh copy.

How is a closure different from a global variable, if both let multiple functions share state?

A global variable is reachable and mutable from literally anywhere in the program; a closure's variables are only reachable through the specific functions that were created with access to them, which is what makes closures useful for encapsulation and globals a common source of bugs.