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.
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()));3 10 [ 3, 3, 3 ] [ 0, 1, 2 ]

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.