What is a closure in JavaScript?

Intermediate12 min interview
Skills tested:
Lexical scope and scope chainClosure memory and variable retentionModule pattern and data encapsulation with closuresStale closure bugs in React hooksLoop closure bug with var

Advertisement

🧩 Scenario

In a real codebase, closures appear in React hooks, factory functions, event handlers, and the module pattern. Every function in JavaScript closes over its surrounding lexical environment, meaning it retains access to variables from its outer scope even after that scope's execution has finished. The most common sources of closure-related bugs in production are stale closures in useEffect and the loop variable capture problem with var.

Architecture Walkthrough

What a Closure Is

A closure is a function bundled together with references to the variables in its surrounding lexical environment. When a function is defined inside another function, the inner function retains access to the outer function's variables even after the outer function has returned and its execution context has been removed from the call stack.

Every function in JavaScript forms a closure over its lexical environment. This is not a special case; it is how JavaScript scope works at the engine level. The inner function does not store a copy of the outer variable's value; it stores a reference to the variable binding itself. If the variable is later updated, the closure sees the updated value.

Closures and Private State

The most practical use of closures is creating private state: data that is accessible to specific functions but not to the outside world. A factory function can return a set of methods that share access to variables in the factory's scope. Those variables cannot be read or modified directly from outside the factory's return value.

This is the foundation of the module pattern, which was the standard way to encapsulate private logic before ES modules existed. It is also how React's useState hook conceptually works: the state value lives in a closure that only the returned getter and setter can access.

Stale Closures in React

A stale closure occurs when a closure captures a variable at one point in time and continues to use that captured reference even after the variable has been updated. In React, this most commonly happens inside useEffect when a dependency is not listed in the dependency array. The effect's callback closes over the initial value of a prop or state variable and never sees updates to it.

The fix is always the same: ensure the effect's dependency array includes every value from the component's scope that the effect reads. React's exhaustive-deps ESLint rule exists specifically to catch this pattern.


Key Code Explained

// Basic closure: inner function retains access to outer variable
function makeCounter(start = 0) {
  let count = start; // private to this closure
  return {
    increment: () => ++count,
    decrement: () => --count,
    value: () => count,
  };
}

const counter = makeCounter(10);
counter.increment(); // 11
counter.increment(); // 12
counter.decrement(); // 11
counter.value(); // 11
// count is inaccessible directly from outside makeCounter

// Closure captures reference, not value
function makeMultiplier(factor) {
  return (n) => n * factor; // factor is closed over
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
double(5); // 10
triple(5); // 15

// Stale closure in React (incorrect)
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1); // count is stale — captured at effect creation
  }, 1000);
  return () => clearInterval(id);
}, []); // missing count in deps

// Stale closure fix: use functional update form
useEffect(() => {
  const id = setInterval(() => {
    setCount((prev) => prev + 1); // reads latest value, no closure dependency
  }, 1000);
  return () => clearInterval(id);
}, []);

The makeMultiplier example shows a core strength of closures: each call to makeMultiplier creates an independent closure with its own factor. double and triple do not share their factor variable; they each have their own private copy of the outer scope.


Tradeoffs

PatternProCon
Closure for private stateNo class needed, lightweight encapsulationVariables live in memory as long as the closure exists
Class for private stateFamiliar OOP pattern, explicit structureMore verbose, private fields require # syntax in modern JS
Module (ES imports)Native language-level privacyFile-level granularity, cannot create per-instance private state

What Interviewers Actually Check

  • Whether you can define a closure in plain terms without relying on jargon
  • Whether you understand that closures capture references, not values
  • Whether you can trace what a closed-over variable's value will be at the time the closure is invoked
  • Whether you can identify and fix a stale closure in a React hook
  • Whether you know that every function in JavaScript forms a closure, not just factory functions

Follow-Up Questions

  1. If you create 1000 closures in a loop and each closes over a large object, what memory concern would you raise?
  2. How does a closure differ from a class in terms of encapsulating private state?
  3. Can you implement a once utility function using a closure that allows a function to be called only one time?
  4. How does useCallback in React use closures, and when does it create stale closure bugs of its own?
  5. A debugger shows a closure retaining a large DOM node after the node has been removed from the page. What is happening and how do you fix it?

Common Candidate Mistakes

  • Thinking closures only exist in factory functions, when every function in JavaScript closes over its lexical scope
  • Expecting a closure to capture the value of a variable at the moment of creation rather than a reference to the binding
  • Not accounting for stale closures in useEffect, useCallback, and useMemo in React
  • Using var in a loop and expecting each iteration's closure to capture a distinct value when all share one binding
  • Assuming the closed-over variable is garbage collected when the outer function returns, when in fact it persists as long as the closure is reachable

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you define a closure in one sentence without using the word closure?
  • Can you write a counter function that uses a closure to maintain private state?
  • Can you explain why a closed-over variable is not garbage collected after the outer function returns?
  • Can you describe the stale closure problem in React hooks and how to prevent it?
  • Can you explain how the module pattern uses closures to create private variables?

Summary

A closure is the combination of a function and the lexical environment in which it was defined. The inner function retains a live reference to variables in its outer scope, not a snapshot of their values at the moment the closure was created. This reference persists as long as the closure itself is reachable, which is why a factory function's local variables are not garbage collected when the factory returns.

Closures power private state in the module pattern, factory functions, and React hooks. The key pitfall is stale closures, where a callback captures an outdated reference because the dependency that caused the variable to update was not reflected in the closure's scope chain. In React this typically manifests in effects and memoized callbacks that read props or state without listing them as dependencies.

Understanding closures at the reference level rather than the value level is the signal interviewers look for: not just knowing that closures exist, but being able to trace exactly which variable a closure sees and when.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

Why are closures important?

They enable data privacy and maintain state between function calls without global variables.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement