stackademic

The leading education platform for anyone with an interest in software development.

Closures

Functions that remember variables from their enclosing scope

Overview

A closure is created when a function accesses variables from an outer function's scope, even after the outer function has returned. Closures power private state, factories, and callbacks.

Syntax / Usage

function makeCounter(start = 0) {
  let count = start
  return {
    increment() { count += 1; return count },
    decrement() { count -= 1; return count },
    value() { return count },
  }
}

const counter = makeCounter(10)
counter.increment()  // 11
counter.value()      // 11

// Closure in loop (use let)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)  // 0, 1, 2
}

Examples

Memoize expensive calculations:

function memoize(fn) {
  const cache = new Map()
  return (...args) => {
    const key = JSON.stringify(args)
    if (cache.has(key)) return cache.get(key)
    const result = fn(...args)
    cache.set(key, result)
    return result
  }
}

const fib = memoize((n) =>
  n < 2 ? n : fib(n - 1) + fib(n - 2)
)

Event handler with partial application:

function onClick(id) {
  return () => console.log('Clicked', id)
}

buttons.forEach((btn) => {
  btn.addEventListener('click', onClick(btn.dataset.id))
})

Common Mistakes

  • Classic var in loops creating closures that all see the final value
  • Memory leaks from closures holding large objects longer than needed
  • Assuming this inside closures follows outer this—arrow functions use lexical this
  • Debugging stale closures when state updates rely on old captured values

See Also

functions promises async-await array-methods