Back to JavaScript
JavaScript
easy
mid

What is a closure and where have you used one in practice?

Closure = function + the variables of its enclosing scope, kept alive because the function references them. Real uses: private state in factories/modules, memoization caches, partial application/currying, React hooks capturing renders' props, debounce/throttle holding their timer, event handlers needing per-instance config.

3 min read·~6 min to think through

See [[closures-and-lexical-scoping]] for the deeper mechanics. This question is usually a "tell me a real example" softball.

Definition

A function that retains access to variables from the scope where it was defined, even after that scope has returned.

Real-world examples

Private state in a factory

js
function createCounter() {
  let n = 0;
  return {
    inc: () => ++n,
    get: () => n,
  };
}

n is private; only the returned methods can touch it. Module pattern in a nutshell.

Memoization

js
function memoize(fn) {
  const cache = new Map();
  return (key) => {
    if (!cache.has(key)) cache.set(key, fn(key));
    return cache.get(key);
  };
}

cache lives across calls — held alive by the closure.

Debounce / throttle

js
function debounce(fn, ms) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
}

t survives between calls because the returned function captures it.

Partial application

js
const add = (a) => (b) => a + b;
const add5 = add(5);
add5(2);   // 7

The inner (b) => a + b closes over a.

React hooks

Every render is a new closure. useState, useEffect, useCallback work by capturing the current render's values:

jsx
const [count, setCount] = useState(0);
useEffect(() => { console.log(count); }, [count]);
// Each effect callback captures THAT render's count.

If you write useEffect(() => { setInterval(() => log(count), 1000); }, []) with empty deps, the interval closure captures the initial count — the classic stale-closure bug.

Event handlers with per-row state

jsx
items.map((item) => <button onClick={() => onSelect(item.id)}>...</button>)

The onClick closes over item.id for that row.

Configuration baked into a factory

js
function makeLogger(prefix) {
  return (msg) => console.log(`[${prefix}]`, msg);
}
const error = makeLogger("ERROR");
error("boom");

What I'd say in an interview

Pick one example and tell a story:

"I used closures recently to build a small toast queue. The factory returns { enqueue, subscribe }; the queue array and listener set are captured in the closure, so nothing outside the module can mutate them — encapsulation without a class. The subscribe function returns an unsubscribe that closes over the listener reference. It's how Zustand, Redux, and event emitters work under the hood."

Or React-specific:

"Every time I write useEffect, I'm relying on closures — the effect callback captures that render's state and props. Forgetting that gives you stale-closure bugs where an interval logs an old value. The fix is either listing the value in deps or reading from a ref."

Interview framing

"A closure is a function plus its enclosing scope's variables. Practical uses: private state in factories (module pattern, Zustand stores), memoization caches, debounce/throttle holding timers, partial application, React hooks capturing per-render values, event handlers carrying per-item context. The classic gotcha is stale closures — useEffect with empty deps logging the initial value forever. Pick one real example and tell the story rather than reciting the definition."

Follow-up questions

  • Walk through a stale closure bug.
  • Closures vs classes for private state — when which?
  • How do closures cause memory leaks?

Common mistakes

  • Reciting the definition without an example.
  • Using var in loops + closures (classic interview pitfall).
  • Stale closures from missing useEffect deps.

Performance considerations

  • Captured variables stay alive; large objects in long-lived closures can leak.

Edge cases

  • Closures over loop variables (let vs var).
  • Holding refs to large objects in long-lived closures.

Real-world examples

  • Toast queues, throttle/debounce, React hooks, Zustand stores, event emitters, factories.

Senior engineer discussion

Seniors connect closures to React's render model (every render is a new closure), memory implications, and module-pattern history.

Related questions