DNA⚡ JavaScriptClosures & Lexical Scope
ðŸĨšEggJavaScriptFundamentals

Closures & Lexical Scope

Closures aren't just an interview topic — they're the mechanism behind React hooks, module patterns, and half of functional programming in JS.

Closures & Lexical Scope

If you're asked about closures in a senior interview and you give a textbook definition, you've already lost. Senior engineers explain closures through real-world implications.

What is a Closure?

A closure is a function that retains access to its lexical scope even when executed outside that scope.

function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    getCount: () => count,
  };
}
 
const counter = createCounter();
counter.increment();
counter.increment();
counter.getCount(); // 2

count is not accessible from outside, but the returned functions close over it. This is encapsulation without classes.

Why Closures Matter in React

Every React hook relies on closures:

function Counter() {
  const [count, setCount] = useState(0);
 
  // This callback closes over `count`
  const handleClick = () => {
    console.log(count); // Always the value from this render
  };
 
  return <button onClick={handleClick}>{count}</button>;
}

The Stale Closure Problem

This is the #1 closure-related bug in React:

function Timer() {
  const [count, setCount] = useState(0);
 
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // Bug: `count` is always 0
    }, 1000);
    return () => clearInterval(id);
  }, []); // Empty deps = closure captures initial `count`
}

Fix: Use the functional updater form:

setCount(prev => prev + 1);

This avoids the stale closure entirely because you're not reading count — you're telling React to compute the next value from the previous one.

Senior-Level Pattern: Closures for Encapsulation

function createAPI(baseURL) {
  const token = getStoredToken();
 
  return {
    get: (path) => fetch(`${baseURL}${path}`, {
      headers: { Authorization: `Bearer ${token}` },
    }),
    post: (path, data) => fetch(`${baseURL}${path}`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    }),
  };
}

The token and baseURL are private. No class needed, no this binding issues.

Interview Signal

When interviewers ask about closures, they want to see:

  1. Practical understanding — not just "a function that remembers its scope"
  2. Awareness of pitfalls — stale closures, memory leaks
  3. Architectural application — module patterns, factory functions, hook internals