Understanding JavaScript Closures
A beginner-friendly explanation of closures with practical examples you'll actually recognize from code you've already written.
Closures trip a lot of developers up early on, not because the idea itself is hard, but because it usually gets explained with a dry, abstract definition instead of a reason to actually care. Here's the version that tends to stick, with enough examples that you'll start noticing closures everywhere in code you've already written.
What it really is
A closure is a function that remembers the variables from the place it was created, even after that outer function has already finished running and technically no longer exists. That's the whole idea. Everything else is just seeing why it's useful in practice, and once you see one real use case, the rest tend to click quickly.
A quick example
function makeCounter() {
let count = 0;
return () => ++count;
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
The inner function keeps its grip on count long after makeCounter has already returned and finished. It's not reset with each call, it's quietly remembered, privately, by the closure itself. Nothing outside that function can reach into count directly, which turns out to be genuinely useful rather than just a curiosity.
A closure is just a function with a memory of where it was born.
Where you've already used one, whether you noticed or not
Every time you call useState in React, debounce a search input so it doesn't fire on every keystroke, or write a small module that keeps some internal state hidden from the rest of your app, a closure is doing the actual work behind the scenes. You've likely been relying on this pattern for a while without needing the formal name for it.
A second example: the module pattern
function createBankAccount(balance) {
return {
deposit: (amt) => balance += amt,
getBalance: () => balance
};
}
Here, balance is completely private. Nothing outside those two returned functions can touch it directly, no accidental overwrite from somewhere else in your codebase. That privacy isn't a special feature you had to opt into, it's just what a closure naturally gives you for free, and it's the same underlying pattern that made the counter example work.
A third example: closures in event handlers
function setupButton(label) {
const btn = document.createElement("button");
btn.textContent = label;
btn.onclick = () => alert("You clicked: " + label);
return btn;
}
Each button created by this function remembers its own label, permanently, even though setupButton already finished running the moment the button was created. If you called this function five times with five different labels, you'd get five buttons, each correctly remembering its own value. This is the same closure behavior showing up in a completely different, very common context.
The classic mistake
Looping with var and expecting each iteration to remember its own separate value is the bug almost every JavaScript developer hits at least once. var isn't scoped per loop iteration, so every closure created inside that loop ends up sharing the exact same final value once the loop finishes. Switch to let, which creates a fresh binding on each pass, and the problem just disappears.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// logs 3, 3, 3
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// logs 0, 1, 2
Try this today
Find one place in your code using var inside a loop with a callback, and rewrite it with let to see the difference for yourself.
Enjoyed this article?
Get practical tech tips and African tech news straight to your inbox. No spam, unsubscribe anytime.
Subscribe to Techmin