Back to Blog
Javascript

Closures Explained - Javascript

Ever wondered how JavaScript functions remember variables even after their scope ends? That magic is called a closure—let’s unpack it!

Wajahat Zia
Wajahat Zia
9/9/2025
0 views

Closure in JavaScript Explained (Made Simple)

Closures are one of those concepts in JavaScript that most developers use without even realizing it. But to truly master the language, it’s important to understand what closures are, why they exist, and how they behave. Closures are a fundamental building block of JavaScript — without them, many patterns and libraries you use every day simply wouldn’t be possible.

In this article, we’ll break closures down step by step with simple examples. We’ll stick to modern JavaScript using let and const. (The older var has some quirks, which we’ll cover another time.)

Functions and Scope

JavaScript is a function-oriented language. Functions can be created at any point, passed around like values, and executed later in completely different parts of your program.

But this raises an important question:

👉 When a function is executed later, what happens to the variables from the scope in which it was originally created?

To answer that, let’s quickly revisit block scope.

Block Scope Recap

When you declare a variable inside a block { ... }, it’s only accessible within that block:

javascript
{
let foo = "bar";
console.log(foo); // "bar"
}
console.log(foo); // ReferenceError: foo is not defined

This is useful in if, for, and while statements because it prevents variables from “leaking” outside.

Functions Inside Functions

Now let’s mix block scope with functions:

javascript
function makeCounter() {
let count = 0;
return function increment() {
return count++;
};
}
let counter = makeCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2

Here’s what’s happening:

makeCounter creates a local variable count.
Instead of returning a value, it returns the inner function increment.
Even after makeCounter has finished running, the increment function still remembers the variable count from its outer scope.

This “remembering” of variables is exactly what a closure is.

A closure is simply:

Independent Closures

Now, let’s check if closures share data or not:

javascript
let counter1 = makeCounter();
let counter2 = makeCounter();
console.log(counter1()); // 0
console.log(counter2()); // 0
console.log(counter1()); // 1
console.log(counter2()); // 1

Each call to makeCounter() creates a new lexical environment with its own count. That’s why counter1 and counter2 work independently.

If closures didn’t exist, both counters would be modifying the same shared variable — and that would be chaos!

Lexical Environment (Behind the Scenes)

Closures work because of something called the Lexical Environment.

Although you can’t access it directly in code, it’s part of how JavaScript is defined. Every block or function internally has a Lexical Environment, which contains:

1.
Environment Record → stores all local variables in that block.
2.
A reference to the outer Lexical Environment → this creates the “chain” of scopes.

Example:

javascript
let phrase = "foo";
function completePhrase(text) {
return `${phrase} ${text}`;
}
console.log(completePhrase("bar")); // "foo bar"

Here’s the breakdown:

The lexical environment of completePhrase contains the variable text.
It also has a link to the outer environment, where phrase is stored.
When phrase is used inside the function, JavaScript looks it up in the chain and finds it in the outer scope.

This is the mechanism that powers closures.

Why Closures Matter

Closures may feel abstract, but they are extremely practical. They let us:

Encapsulate state (like the counter example).
Create private variables (useful in modules and object factories).
Write callbacks and event handlers that remember context.
Build powerful abstractions in libraries and frameworks.

For example, here’s a closure used to hide data:

javascript
function createUser(name) {
let balance = 0;
return {
getName() {
return name;
},
deposit(amount) {
balance += amount;
},
getBalance() {
return balance;
}
};
}
let user = createUser("Alice");
user.deposit(100);
console.log(user.getBalance()); // 100
console.log(user.name); // undefined (private!)

Here, balance is completely private — it can’t be accessed directly, only through the functions returned. This is made possible by closures.

Final Thoughts

Closures may sound intimidating at first, but the rule is simple:

👉 Functions remember the environment they were created in.

That’s it. Everything else is just detail. Once you grasp this, closures stop being “magic” and start feeling like one of JavaScript’s most powerful features.

JavascriptFundamentals