Closures power data privacy, memoization, and the module pattern. But they keep parent scopes alive in memory, so clean up when done.
You can read Practical Uses of Closures and try the first 1 exercise on the free plan. Upgrade to unlock the remaining 2 exercises and continue the full lesson flow.
Pro: every unit of every course · unlimited hearts · no weekly limit on assessment attempts
Complete lesson & earn 250 PX
EXERCISE
1Closures let you create truly private variables. No one outside the closure can read or modify them directly. This is JavaScript version of private fields.
A variable inside a function is unreachable from outside it. Combine that with a closure and you have private state that only chosen functions can touch, which is the basis of the module pattern.
function createCounter() {
let count = 0;
return {
increment() {
count += 1;
return count;
},
reset() {
count = 0;
return count;
},
value() {
return count;
},
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.value()); // 2
console.log(counter.count); // undefined
The returned object has three methods and no count property. There is no path from the outside to the variable, so it cannot be read, written, or accidentally deleted by unrelated code.
Compare that with the obvious alternative:
const counter = { count: 0 };
counter.count = "banana";
Nothing prevents this. Any code holding the object can put anything in the property, and the failure will surface later in whatever reads it.
Privacy also lets the accessor do work:
function createTemperature() {
let celsius = 0;
return {
set(value) {
if (typeof value !== "number") return "not a number";
celsius = value;
return celsius;
},
fahrenheit() {
return celsius * 9 / 5 + 32;
},
};
}
const temp = createTemperature();
console.log(temp.set(100)); // 100
console.log(temp.fahrenheit()); // 212
console.log(temp.set("hot")); // "not a number"
console.log(temp.fahrenheit()); // 212, unchanged
fahrenheit is derived on demand rather than stored, so it can never disagree with celsius. And the rejected value left the state untouched, because the only way in runs the check first.
This is the same encapsulation a class gives you, built from nothing but a function and the scope rules from unit 3.
The pattern generalises to anything with rules about how its state may change. A queue that must never go negative, a connection that must be opened before use, a form that must be validated before submission: in each case the state lives in the closure and the returned functions are the only way to reach it.
function createQueue() {
const items = [];
return {
add(item) {
items.push(item);
return items.length;
},
take() {
if (items.length === 0) return null;
return items.shift();
},
size() {
return items.length;
},
};
}
const q = createQueue();
q.add("a");
q.add("b");
console.log(q.take()); // "a"
console.log(q.size()); // 1
take on an empty queue returns null instead of undefined from a failed shift, because the function gets to decide what empty means. Exposing items directly would hand that decision to every caller.
Use closures for private variables (module pattern), results (memoization), and . Set closure references to null when no longer needed to free memory.