Understand how closures allow inner functions to retain access to variables from parent scopes with examples.
let so that each iteration receives its own distinct variable binding.Closure definitionKeeps reference access to outer scope variables even after parent execution finishes.
function outer() {
let n = 0;
return () => ++n;
}
const count = outer();
count(); // 1Scope nestingForms closures automatically whenever you nest child functions inside parent contexts.
function parent() {
const x = 1;
function child() { return x; }
}Memory persistenceRetains outer scope values in memory as long as the child function exists.
const fn = outer(); // n stays in memoryLexical scopeResolves variable scopes statically based on where the functions are declared.
const x = 10;
function test() { console.log(x); }var in loopShares a single variable reference across all loop callbacks, causing bugs.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
// logs 3, 3, 3let in loopCreates a new variable binding block per loop iteration to fix sharing.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i));
}
// logs 0, 1, 2IIFE captureCaps variable values per iteration loop by wrapping functions in IIFE scopes.
for (var i = 0; i < 3; i++) {
(v => setTimeout(() => console.log(v)))(i);
}State encapsulationStores internal variable values safely away from the global execution context.
function createCounter() {
let count = 0;
return {
increment: () => ++count,
get: () => count
};
}Public API accessExposes interface methods to read and write private variables under control.
const c = createCounter();
c.increment();
console.log(c.get()); // 1Accidental mutationPrevents external script scripts from corrupting or writing internal state values directly.
let c = createCounter();
// c.count is undefinedBehavior configurationCreates functions sharing standard behaviors but retaining distinct internal configurations.
function makeAdder(x) {
return y => x + y;
}
const add5 = makeAdder(5);
add5(10); // 15Private memoizationCloses over a private Map cache to return cached function outputs.
function memoize(fn) {
const cache = new Map();
return x => {
if (cache.has(x)) return cache.get(x);
const res = fn(x);
cache.set(x, res);
return res;
};
}Closures inside a var loop share one variable reference. The loop terminates before the callbacks execute. Change the declaration to let to bind a fresh variable index per iteration.
Declare local variables inside a parent function and return helper functions accessing them. The returned helpers close over the state. External operations cannot inspect or alter this private state directly.
A function becomes a closure when it references variables outside its scope after the parent context exits. The closure keeps these external variables alive in memory. Normal functions only use parameters.
A factory function accepts configuration values and returns specialized functions enclosing those values. For example, makeAdder(5) returns a helper that always adds five. This keeps logic parameterized and clean.
Avoid capturing large object references that you do not need. Destructure only specific values required by the inner function. Nullify large variable handles once they are no longer needed.
Debouncing Input with Closures
A debounce factory function closes over a timer reference to ensure that rapid handlers only execute once typing pauses.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}The closure over the timer variable keeps it alive between calls without polluting the global variable namespace.