JavaScript Closures

Understand how closures allow inner functions to retain access to variables from parent scopes with examples.

TL;DR

  1. 01Ensure inner functions retain access to their defining parent scopes.
  2. 02Store persistent private data state safely without using global variables.
  3. 03Resolve outer variables based on where functions are statically defined.

Tips

  1. 01Expose public API methods while keeping raw state hidden inside an enclosing closure scope function.
  2. 02Choose closures over standard class definitions when you only need to store small private states.

Warnings

  1. 01Avoid creating unnecessary closures enclosing huge objects because they can generate substantial memory leaks.
  2. 02Declare loop indexes using let so that each iteration receives its own distinct variable binding.

What Closures Are

    Closure definition

    Keeps reference access to outer scope variables even after parent execution finishes.

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

    Forms closures automatically whenever you nest child functions inside parent contexts.

    function parent() {
      const x = 1;
      function child() { return x; }
    }
    Memory persistence

    Retains outer scope values in memory as long as the child function exists.

    const fn = outer(); // n stays in memory
    Lexical scope

    Resolves variable scopes statically based on where the functions are declared.

    const x = 10;
    function test() { console.log(x); }

Closures and Loops

    var in loop

    Shares a single variable reference across all loop callbacks, causing bugs.

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

    Creates a new variable binding block per loop iteration to fix sharing.

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

    Caps variable values per iteration loop by wrapping functions in IIFE scopes.

    for (var i = 0; i < 3; i++) {
      (v => setTimeout(() => console.log(v)))(i);
    }

Private Variables

    State encapsulation

    Stores internal variable values safely away from the global execution context.

    function createCounter() {
      let count = 0;
      return {
        increment: () => ++count,
        get: () => count
      };
    }
    Public API access

    Exposes interface methods to read and write private variables under control.

    const c = createCounter();
    c.increment();
    console.log(c.get()); // 1
    Accidental mutation

    Prevents external script scripts from corrupting or writing internal state values directly.

    let c = createCounter();
    // c.count is undefined

Function Factories

    Behavior configuration

    Creates functions sharing standard behaviors but retaining distinct internal configurations.

    function makeAdder(x) {
      return y => x + y;
    }
    const add5 = makeAdder(5);
    add5(10); // 15
    Private memoization

    Closes over a private Map cache to return cached function outputs.

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

In Practice

FAQ