JavaScript Closures Explained—Professionally, Calmly, and Then Not At All


Lately I’ve been using a lot of AI tools. At some point, I realized I’d been coasting—copy-pasting more than thinking. This is me getting back to the roots. Today, that means JavaScript closures.
This guide is short, clean, and to the point. Just one idea, explained simply. With examples. And maybe a few side effects.
What is a Closure?
A closure is a function that remembers the variables around it—even after those variables should be gone.
(That “remembered” environment is called the function’s lexical scope
.)
That's it. It holds on.
When a function is created inside another function, it keeps a link to the outer stuff. Even after the outer function runs and ends, the inner one still has access.
(This is because functions in JavaScript form closures
when they reference variables from their outer execution context
.)
Why Is That a Big Deal?
Usually, when a function runs, its local variables disappear when it’s done.
(This is part of the normal call stack
and garbage collection
process.)
But if that function returns another function—or passes one around—the inner function keeps a reference to the old variables. They don’t get deleted.
It’s like those leftovers in the back of your fridge. Still there. Still alive.
A Simple Example
function counter() {
let count = 0;
function increment() {
return ++count;
}
return increment;
}
const userCounter = counter();
const tableCounter = counter();
userCounter(); // 1
userCounter(); // 2
tableCounter(); // 1
tableCounter(); // 2
Each call to counter()
creates a new execution context
with its own count
.
The returned function (increment
) still has access to that scope—it closes over count
, creating a closure
.
Less Simple. Not More Normal.
function createAgent(name) {
let mission = "classified";
return function reveal() {
console.log(`${name} is on a ${mission} mission. Please act normal.`);
};
}
const bond = createAgent("Bond");
const raccoon = createAgent("Agent Raccoon");
bond(); // Bond is on a classified mission. Please act normal.
raccoon(); // Agent Raccoon is on a classified mission. Please act normal.
The reveal()
function is returned and still references name
and mission
. That’s closure behavior: preserving access to variables in the outer lexical environment.
Another One (Still Technically Educational)
function dramaLlama(name) {
let level = 0;
return function escalate() {
level++;
console.log(`${name} is now at drama level ${level}. Stay alert.`);
};
}
const karen = dramaLlama("Karen");
karen(); // Karen is now at drama level 1
karen(); // Karen is now at drama level 2
That level
variable? It’s part of the dramaLlama
scope—but escalate()
still knows about it. That's the closure doing its job. This is sometimes called stateful function behavior
.
Use It For:
Keeping state across function calls (
persistent state
)Making function factories (
function currying
orpartial application
)Creating “private” variables (
encapsulation
)Handling async code where you need context (
callbacks
,event listeners
,setTimeout
)
That’s It
Closures are simple. They’re weird if you stare too long, but the idea is small.
A function. Remembering stuff. That’s all.
If you got through this without blinking at the llama, you’re doing great.
Subscribe to my newsletter
Read articles from Agnibha Chatterjee directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
