← Back to Chapters

JavaScript Closures

? JavaScript Closures

? Quick Overview

A closure is created when a function remembers and can access variables from its outer (lexical) scope, even after that outer function has finished executing.

Closures allow JavaScript functions to maintain state, hide data, and build powerful abstractions that are used everywhere in modern code (callbacks, event handlers, modules, and more).

? Key Concepts

  • Lexical Scope: A function’s available variables are determined by where it is written in the code.
  • Inner Function: A function defined inside another function can access the outer function’s variables.
  • Closure: The combination of a function and the lexical environment it remembers.
  • Persistent State: Variables from the outer scope stay “alive” as long as the inner function exists.
  • Encapsulation: Closures can hide implementation details and expose only what you want.

? Syntax and Theory

Typical closure pattern in JavaScript:

  • Define an outer function that declares some local variables.
  • Define and return an inner function from inside the outer function.
  • Call the outer function and store the returned inner function.
  • Each call to the inner function can still access the original outer variables.

? Code Examples

? Basic Counter with Closure

? View Code Example
function outer() {
  let count = 0;

  function inner() {
    count++;
    console.log(count);
  }

  return inner;
}

const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3

➕ Closure with Parameters (Adder)

? View Code Example
function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
console.log(add5(10)); // 15
const add10 = makeAdder(10);
console.log(add10(3)); // 13

? Closures for Data Privacy

? View Code Example
function secretBox() {
  let secret = "Hidden";

  return {
    getSecret: () => secret,
    setSecret: (value) => (secret = value),
  };
}

const box = secretBox();
console.log(box.getSecret()); // Hidden
box.setSecret("New value");
console.log(box.getSecret()); // New value

? Closures in Loops

? View Code Example
for (let i = 1; i <= 3; i++) {
  setTimeout(() => {
    console.log("Iteration:", i);
  }, 1000 * i);
}

? Output and Explanation

  • In the counter example, the variable count lives in the outer function outer. The inner function inner forms a closure over count, so every call to counter() increments the same shared value: 1 → 2 → 3.
  • In makeAdder, each call (e.g. makeAdder(5)) creates a new closure with its own copy of x. That is why add5 and add10 remember different values.
  • In secretBox, the variable secret is private to the closure. It cannot be accessed directly from outside, only through getSecret and setSecret.
  • In the loop example, using let ensures each iteration has its own i value. Each arrow function scheduled with setTimeout closes over the correct i.

?️ Use Cases / When to Use Closures

  • Data Privacy: Hide internal variables and expose only safe methods.
  • Stateful Functions: Build counters, accumulators, and configuration-based functions.
  • Callbacks & Event Handlers: Keep context around when code runs asynchronously.
  • Module Patterns: Structure code into logical units with private state.
  • Functional Utilities: Build reusable higher-order functions (e.g., makeAdder).

? Closures and Memory

Because closures keep references to outer variables, those variables stay in memory as long as the inner function is reachable. This is normally fine and very useful, but:

  • Avoid storing huge objects in closures unless necessary.
  • Release references (set to null or reassign) if they are no longer needed.
  • Be mindful when using closures inside long-lived objects or global variables.

? Tips & Best Practices

  • Use closures to create factory functions that generate customized logic (e.g. makeAdder).
  • Encapsulate state in closures instead of using global variables or scattered mutable state.
  • Remember that variables in closures are references, not snapshots; if the value changes, all closures see the updated value.
  • Prefer let and const in loops to avoid unexpected behavior in asynchronous callbacks.
  • Keep closure-based modules small and focused so they remain easy to understand and debug.

? Try It Yourself

  • Create a function multiplier(n) that returns a function multiplying its input by n, e.g. double = multiplier(2).
  • Build a counter with closures that supports increment(), decrement(), and reset() methods, all sharing the same internal count.
  • Use closures with setTimeout to print numbers 1 through 5 with a 1-second gap between each log.
  • Implement a simple “once” function that runs a callback only the first time it’s called and then remembers the result.