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:
{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:
function makeCounter() {let count = 0;return function increment() {return count++;};}let counter = makeCounter();console.log(counter()); // 0console.log(counter()); // 1console.log(counter()); // 2
Here’s what’s happening:
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:
let counter1 = makeCounter();let counter2 = makeCounter();console.log(counter1()); // 0console.log(counter2()); // 0console.log(counter1()); // 1console.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:
Example:
let phrase = "foo";function completePhrase(text) {return `${phrase} ${text}`;}console.log(completePhrase("bar")); // "foo bar"
Here’s the breakdown:
This is the mechanism that powers closures.
Why Closures Matter
Closures may feel abstract, but they are extremely practical. They let us:
For example, here’s a closure used to hide data:
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()); // 100console.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.