JavaScript Closures

Learn how closures let functions remember variables from their outer scope with clear examples.

TL;DR

  1. 01Inner functions remember variables from their parent scope.
  2. 02Use closures to keep data private and persistent.
  3. 03Lexical scope means variables come from where functions are defined.

Tips

  1. 01Use the module pattern with closures to expose only the functions you want, keeping internal state hidden from outside code.
  2. 02Reach for closures over classes when you need a small amount of private state and just a couple of methods.

Warnings

  1. 01Closures keep their captured variables in memory, so avoid creating many closures over large objects to prevent memory leaks.
  2. 02Using var instead of let inside a loop makes every closure share one variable, a classic source of off-by-one bugs.

What Closures Are

    Closure

    A function that keeps access to its outer scope after that scope returns.

    function outer() {
      let n = 0;
      return () => ++n;
    }
    const count = outer();
    count(); // 1
    Nesting

    Closures form automatically whenever you nest one function inside another.

    Memory

    Outer variables stay alive as long as the inner function referencing them exists.

    Lexical scope

    Variables resolve based on where a function is defined, not where it's called from.

    Every function

    Every JavaScript function creates a closure over its surrounding scope.

Closures and Loops

    var in a loop

    All closures inside a var-based loop share the same variable reference.

    for (var i = 0; i < 3; i++) {
      setTimeout(() => console.log(i));
    }
    // logs 3, 3, 3
    let in a loop

    let creates a fresh binding on every iteration.

    for (let i = 0; i < 3; i++) {
      setTimeout(() => console.log(i));
    }
    // logs 0, 1, 2
    IIFE workaround

    Wrap the loop body in an IIFE to capture the current value with var.

    for (var i = 0; i < 3; i++) {
      (() => console.log(i))();
    }
    // logs 0, 1, 2
    Common bug

    This is one of the most common closure bugs — prefer let in loops by default.

    forEach

    The same var-sharing issue shows up in forEach callbacks that rely on var.

Private Variables

    Hide state

    Closures let you hide internal state from the rest of your code.

    function createCounter() {
      let count = 0;
      return { increment: () => ++count, get: () => count };
    }
    const c = createCounter();
    c.increment(); // 1
    Expose methods

    Return an object with methods that read and update the private values.

    Controlled access

    The outside world can only change state through the methods you provide.

    Common uses

    This pattern suits counters, configuration, and small data stores.

    Safety

    Private variables prevent accidental changes from unrelated code.

Function Factories

    Share behavior

    Create functions that share behavior but each remember their own values.

    function memoize(fn) {
      const cache = new Map();
      return x => cache.has(x) ? cache.get(x) : cache.set(x, fn(x)).get(x);
    }
    const square = memoize(x => x * x);
    square(5); // 25, cached after
    Private cache

    The closure keeps cache alive and private, with no global variable needed.

    Real-world use

    This pattern powers React's useMemo hook and many utility libraries.

    Any arity

    Add a rest parameter to memoize functions that take multiple arguments.

    Pure functions only

    Reserve memoization for pure functions with consistent, side-effect-free outputs.

In Practice

FAQ