Closures

A closure is a function plus the variables of the scope where it was created. Each function stores a hidden reference, [[Environment]], to the environment record holding that scope's variables. A surviving inner function keeps that record alive after the outer function returns. Every call creates a fresh record, so closures from separate calls never share state.

Two counters with private state, and closures in var and let loopsJavaScriptLive
function makeCounter(step) {
  let count = 0;                                   // survives after makeCounter returns
  return () => (count += step);
}
const byOne = makeCounter(1), byTen = makeCounter(10);
byOne(); byOne();
const withVar = [], withLet = [];
for (var i = 0; i < 3; i++) withVar.push(() => i);   // one shared i
for (let j = 0; j < 3; j++) withLet.push(() => j);   // a new j per iteration
console.log(byOne(), byTen(), withVar.map((f) => f()), withLet.map((f) => f()));
Memory after the counters run: each arrow keeps its own environment record alive
Memory after the counters run: each arrow keeps its own environment record alive

All three var arrows share one function-scoped i, which ends at 3; let creates a binding per iteration. Closures enable private data and memoization (Functional Patterns), but a long-lived listener that closes over a large array or a detached DOM node keeps it in memory until you remove the listener.