A closure is a function that remembers the variables from where it was created, even after that outer function has finished. This gives you private state: in createCounter, nothing outside can change count except through the returned functions.
Closures are everywhere: event handlers, callbacks, setTimeout, and helper factories such as debounce or once.
this depends on how a function is called, not where it is written. Called as obj.method(), this is obj; passed around as a plain callback, it loses that. Arrow functions don't have their own this — they use the surrounding one — which is why they are handy inside methods. fn.bind(obj) fixes this permanently.
"use strict"; // modules and classes are always strict; plain scripts opt in like this
function createCounter(start = 0) {
let count = start; // private: only the functions below can reach it
return {
increment: () => ++count,
reset: () => (count = start),
get value() { return count; },
};
}
const visitors = createCounter();
visitors.increment();
visitors.increment();
console.log(visitors.value, visitors.count); // 2 undefined
function once(fn) {
let done = false;
let result;
return (...args) => {
if (!done) {
done = true;
result = fn(...args);
}
return result;
};
}
const init = once(() => { console.log("initialising..."); return 42; });
console.log(init(), init()); // logs "initialising..." once, then 42 42
const teacher = {
name: "Ms. Lyimo",
students: ["Amina", "Neema"],
greetAll() {
// Arrow callback: uses greetAll's `this`, so this.name works
return this.students.map((s) => `${this.name} greets ${s}`);
},
introduce() {
return `I am ${this.name}`;
},
};
console.log(teacher.greetAll());
const loose = teacher.introduce; // detached from the object
try {
console.log(loose());
} catch (err) {
console.log("Lost this:", err.constructor.name);
}
const bound = teacher.introduce.bind(teacher);
console.log(bound()); // I am Ms. LyimoKey points
- Closures remember outer variables — use them for private state and helper factories.
thisis set by the call site:obj.method()→obj.- Arrow functions inherit
this;bindfixes it for passed-around methods.
Exercise
Write createBankAccount(owner) that returns deposit, withdraw and getBalance functions with the balance kept private, refusing withdrawals larger than the balance. Then write makeMultiplier(n) that returns a function multiplying its input by n.
Show solution
Try the exercise yourself first — then compare your approach with this one.
balance lives only inside createBankAccount, so nothing outside can change it except through the returned functions — real private state. makeMultiplier returns a function that remembers its own n.
function createBankAccount(owner) {
let balance = 0;
return {
deposit(amount) {
if (amount <= 0) throw new RangeError("Deposit must be positive");
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) throw new RangeError(`${owner} has only ${balance}`);
balance -= amount;
return balance;
},
getBalance: () => balance,
};
}
const account = createBankAccount("Amina");
account.deposit(50000);
account.withdraw(20000);
console.log(account.getBalance(), account.balance); // 30000 undefined
try {
account.withdraw(100000);
} catch (err) {
console.log(err.message); // Amina has only 30000
}
const makeMultiplier = (n) => (x) => x * n;
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(21), triple(5)); // 42 15