Programming Techniques

Closures & Lexical Scope

How functions capture their surrounding environment and why this matters for encapsulation.

Overview

A closure is a function bundled with its lexical environment, the set of variables that were in scope when the function was defined, not when it is called. Closures allow functions to "remember" their context across calls and are the foundation of encapsulation in functional languages.

Origin

The term closure was coined by Peter Landin in 1964 in the context of SECD machines and lambda calculus. Closures became a primary encapsulation mechanism in LISP and later in JavaScript, where they compensated for the absence of block scoping before ES6.

Examples

Closures for private state in JavaScript

function makeCounter(initial = 0) {
  let count = initial  // captured by the returned function

  return {
    increment: () => ++count,
    decrement: () => --count,
    value:     () => count,
    reset:     () => { count = initial },
  }
}

const counter = makeCounter(10)
counter.increment()  // 11
counter.increment()  // 12
counter.value()      // 12
// 'count' is inaccessible from outside, true encapsulation

The classic loop closure bug and fix

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

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

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

The var loop bug is one of the most common JavaScript interview questions. Understanding it requires understanding that closures capture variable bindings, not values.

Use Cases

  • 01Encapsulating private state without classes
  • 02Partial application and currying (a closure remembers the first set of arguments)
  • 03Memoization (a closure holds the cache map)
  • 04Event handlers that need access to outer context without global variables
  • 05Module patterns, exporting only what consumers need, hiding implementation details

When Not to Use

  • //When closures hold large objects that prevent garbage collection, this is a common memory leak source
  • //When shared mutable state across multiple closures creates implicit coupling that is hard to trace
  • //When a class with explicit fields communicates intent more clearly

Technical Notes

  • Closures in JavaScript prevent the garbage collector from reclaiming captured variables until the function itself is GC-eligible, common source of leaks in long-lived listeners
  • In Ruby, blocks, procs, and lambdas are all closures. They differ in how they handle return and argument arity
  • Closures are how module systems were built before native ES modules: the revealing module pattern uses an IIFE returning an object
  • Debugging closures in large applications requires understanding which scope chain is captured, browser DevTools let you inspect closure values in breakpoints