Closures, With Real Uses

A function remembering the variables it was born in -- the engine behind privacy, memoisation and stateful callbacks.

Syntaxfunction outer() { let x; return () => x; }

A closure is a function bundled together with the variables that were in scope when it was defined. The inner function keeps those variables alive even after the outer function has returned.

function counter() {
  let n = 0;
  return () => ++n;   // this arrow closes over n
}
const next = counter();
next(); next(); // 1, 2 -- n lives on, private and unreachable from outside

Why it matters in real code

  • Private state -- data no other code can touch, without a class.
  • Memoisation -- cache results in a closed-over map.
  • Factories -- build configured functions like multiplyBy(3).

The famous loop bug

A var in a loop is shared by every closure, so all callbacks see the final value. let creates a fresh binding per iteration and fixes it -- one of the best reasons to have retired var.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Create a private counter that can only be incremented through an exposed function, not directly.
  • Capture a loop variable's value per iteration for async callbacks using a closure.
  • Build a memoisation wrapper that caches previous return values in a closed-over Map.

More examples

Private counter via closure

The returned methods close over count, which is inaccessible from outside makeCounter.

Example · js
function makeCounter() {
  let count = 0;
  return { increment: () => ++count, value: () => count };
}
const c = makeCounter();
c.increment();
c.increment();
console.log(c.value()); // 2

Capture loop variable with IIFE

Wraps each loop body in an IIFE so each closure captures its own copy of i.

Example · js
const fns = [];
for (let i = 0; i < 3; i++) {
  fns.push((x => () => x)(i)); // IIFE captures i per iteration
}
console.log(fns[0](), fns[1](), fns[2]()); // 0 1 2

Memoisation with a closed-over cache

The inner function closes over cache so results persist between calls without a global variable.

Example · js
function memoize(fn) {
  const cache = new Map();
  return function (...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 slowDouble = memoize(n => n * 2);
console.log(slowDouble(5)); // 10 – computed
console.log(slowDouble(5)); // 10 – cached

Discussion

  • Be the first to comment on this lesson.
Closures, With Real Uses — JavaScript | SoundsCode