Glossary

Closure

A closure is a function that 'closes over' its surrounding scope — retaining access to variables from its outer function even after that outer function has finished executing.

Explanation

When a function is defined inside another function, the inner function has access to the outer function's variables. When the outer function returns, you might expect those variables to be garbage collected. But if the inner function is returned or passed somewhere, it keeps a reference to those variables — they're captured in a closure. The variables live on as long as the inner function exists. Closures are how private state is implemented in JavaScript without classes: a factory function creates and returns functions that share access to private variables declared in the factory's scope. Those variables are accessible to the returned functions but invisible to outside code. This is the module pattern, prevalent before ES6 modules. Every JavaScript function creates a closure over its lexical scope at definition time. This is why React hooks work: useState's state value is a closure over React's internal fiber state. useCallback creates a closure over the captured variables at render time — which is why stale closure bugs (referencing outdated values from a previous render) are a common React pitfall. The IIFE (Immediately Invoked Function Expression) pattern was historically used to create module-like scopes: (function() { /* private scope */ })() — everything inside is isolated from the global scope via closure. This pattern predates ES6 modules and explains why you see it in older JavaScript libraries.

Code Example

javascript
// Closure: private state without a class
function createCounter(initialValue = 0) {
  let count = initialValue; // private to this function scope

  return {
    increment() { count++; },
    decrement() { count--; },
    getCount()  { return count; },
    reset()     { count = initialValue; },
  };
}

const counter = createCounter(10);
counter.increment();
counter.increment();
console.log(counter.getCount()); // 12
// console.log(count);           // ReferenceError: count is not defined

// Closure in event handlers (common pattern)
function createButton(label, onClick) {
  const btn = document.createElement('button');
  btn.textContent = label;
  // The handler closes over 'label' — still accessible after createButton returns
  btn.addEventListener('click', () => {
    console.log(`Button "${label}" clicked`);
    onClick();
  });
  return btn;
}

// Classic closure bug: stale values in loops
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // prints 3, 3, 3 (not 0, 1, 2)
}
// Fix: use let (block-scoped) instead of var
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // prints 0, 1, 2
}

Why It Matters for Engineers

Closures are the mechanism behind callbacks, event handlers, React hooks, module patterns, and memoization. If you don't understand closures, you can't understand why stale closure bugs happen in React (useEffect capturing the value of a variable from a previous render), why certain setTimeout callbacks print unexpected values, or how factory functions create private state. Closures are also one of the most-asked JavaScript interview topics. Interviewers test closures because they reveal whether a candidate understands JavaScript's lexical scoping, execution context, and memory model — not just syntax.

Related Terms

Scope · Function · Callback · Event Loop

Learn This In Practice

Go deeper with the full module on Beyond Vibe Code.

Programming Foundations → →