Skip to main content
A closure is a function that retains access to its outer (enclosing) scope even after the outer function has returned. Scope in JavaScript is lexical — determined by where functions are written, not where they’re called.

Scope types

TypeCreated byNotes
GlobalTop-level codeAccessible everywhere
Functionfunction keywordEach call gets its own scope
Block{}, if, forOnly let/const are block-scoped
ModuleES module filePrivate by default

Closures

function counter() {
  let count = 0;

  return {
    increment() { count++; },
    decrement() { count--; },
    value()     { return count; }
  };
}

const c = counter();
c.increment();
c.increment();
console.log(c.value()); // 2
// count is not accessible from outside

Common use cases

  • Data encapsulation — private variables
  • Factory functions — create customized functions
  • Memoization — cache previous results
  • Partial application / currying
// Factory function
function multiplier(factor) {
  return (number) => number * factor;
}

const double = multiplier(2);
const triple = multiplier(3);

double(5); // 10
triple(5); // 15

Classic closure gotcha

// Bug: all callbacks share the same i (var is function-scoped)
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// Prints: 3, 3, 3

// Fix 1: use let (block-scoped, each iteration gets its own i)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// Prints: 0, 1, 2

// Fix 2: IIFE to create new scope per iteration
for (var i = 0; i < 3; i++) {
  ((j) => setTimeout(() => console.log(j), 0))(i);
}

Scope chain

When a variable is accessed, JS looks up the scope chain from innermost to outermost until it finds the variable or reaches the global scope.
const outer = "global";

function first() {
  const outer = "first";

  function second() {
    // outer resolves to "first" (nearest enclosing scope)
    console.log(outer);
  }

  second();
}

first(); // "first"

Common interview questions

A closure is a function bundled together with references to its surrounding lexical environment. It allows a function to access variables from its outer scope even after the outer function has finished executing.
Closures can cause memory leaks if a closure holds a reference to a large object that is no longer needed. The garbage collector cannot reclaim the object because the closure still references it. The fix is to set the reference to null when it’s no longer needed.
function setup() {
  const largeData = new Array(1000000).fill("data");

  return function process() {
    // if process() never uses largeData but captures it,
    // largeData cannot be GC'd as long as process() lives
    return "done";
  };
}
JavaScript uses lexical (static) scope — the scope of a variable is determined at write time by where the variable is declared in the source code. Dynamic scope (not used in JS) would determine scope at runtime based on the call stack. The this keyword behaves somewhat like dynamic scope, which is why arrow functions capture this lexically instead.