A closure in JavaScript is a function that remembers and can access variables from its surrounding lexical scope, even after the outer function has finished executing.
Closures are an important JavaScript concept because they allow functions to preserve access to variables outside their immediate scope. They are useful for creating private data, maintaining state, building callbacks, and writing reusable functions.
In this tutorial, you will learn what closures are, how they work, how to create closures, and how to use them in real-world JavaScript applications.
What Is a Closure in JavaScript?
A closure is created when a function retains access to variables from its surrounding lexical environment.
In simple terms, an inner function can continue accessing variables from an outer function even after the outer function has returned.
Consider the following example:
function outerFunction() {
let message = "Hello, JavaScript!";
function innerFunction() {
console.log(message);
}
return innerFunction;
}
const greet = outerFunction();
greet();
Output:
Hello, JavaScript!
In this example, innerFunction() accesses the message variable declared inside outerFunction().
The outerFunction() call finishes before greet() is called. However, innerFunction() retains access to the lexical environment containing message.
This behavior is called a closure.
How Closures Work in JavaScript
Closures work because JavaScript functions retain access to the lexical environments in which they were created.
A lexical environment contains bindings for variables and other identifiers that are available within a particular scope.
When an inner function references a variable from an outer scope, JavaScript preserves the necessary environment for as long as that reference is needed.
Understanding Closures Step by Step
Consider this example:
function outer() {
let name = "Karim";
function inner() {
console.log(name);
}
return inner;
}
const result = outer();
result();
Output:
Karim
Here is how the code works:
- JavaScript calls
outer(). - The variable
nameis initialized with"Karim". - The inner function
inner()is created and referencesname. - The outer function returns
inner. - The variable
resultstores the returned function. - When
result()is called, the function accessesnamethrough its closure.
Although outer() has completed, the variable name remains accessible because the returned function still references its lexical environment.
Closures Remember Variables, Not Just Their Values
A closure retains access to a variable binding, which means it can observe changes to that variable.
function createCounter() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const counter = createCounter();
counter();
counter();
counter();
Output:
1
2
3
The returned function accesses the same count variable each time it runs.
The first call changes count from 0 to 1. The next call changes it to 2, and the third changes it to 3.
The closure preserves access to the variable’s current value rather than creating a fresh count variable on every call.
JavaScript Closure Examples
Closures appear in many common JavaScript programming patterns. The following examples show how they can be used to preserve state and access variables from outer scopes.
Simple Closure Example
A closure can access an outer variable even when the outer function has already returned.
function greet() {
let message = "Welcome to Web4Learners";
return function () {
console.log(message);
};
}
const sayWelcome = greet();
sayWelcome();
Output:
Welcome to Web4Learners
The returned function remembers the message variable through its closure.
Closure with Parameters
An outer function can receive parameters that remain accessible to its inner function.
function multiplyBy(number) {
return function (value) {
return value * number;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5));
console.log(triple(5));
Output:
10
15
The double function remembers number as 2, while triple remembers number as 3.
Each call to multiplyBy() creates a separate lexical environment, so the two returned functions preserve different values.
Closure with Multiple Functions
An outer function can return an object containing multiple functions that share the same variables.
function createUser() {
let name = "Karim";
return {
getName() {
return name;
},
setName(newName) {
name = newName;
}
};
}
const user = createUser();
console.log(user.getName());
user.setName("Ahmed");
console.log(user.getName());
Output:
Karim
Ahmed
Both methods access the same name variable through their closures. The setName() method updates it, and getName() returns the updated value.
This pattern is useful when you want to control how data is accessed and modified.
Closures and Data Privacy
Closures can help you create private variables in JavaScript. A variable declared inside a function cannot normally be accessed directly from outside that function.
Instead, you can provide inner functions that read or modify the variable.
Creating Private Variables with Closures
Consider the following example:
function createBankAccount() {
let balance = 1000;
return {
deposit(amount) {
if (amount > 0) {
balance += amount;
}
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount();
console.log(account.getBalance());
account.deposit(500);
console.log(account.getBalance());
Output:
1000
1500
The balance variable is declared inside createBankAccount(). It is not exposed as a property on the returned object.
The deposit() and getBalance() methods can access it through their closures.
You cannot directly access the variable using account.balance because no such public property was created.
console.log(account.balance);
Output:
undefined
This is an example of data privacy using closures.
Protecting Data from Direct Modification
Closures allow you to expose controlled operations instead of exposing the underlying variable itself.
function createScore() {
let score = 0;
return {
increase() {
score++;
},
getScore() {
return score;
}
};
}
const game = createScore();
game.increase();
game.increase();
console.log(game.getScore());
Output:
2
The score variable can only be changed through the functions provided by the returned object.
This helps prevent accidental direct modification. However, closure-based privacy alone is not a complete security boundary for sensitive information.
Practical Uses of Closures in JavaScript
Closures are useful in real-world applications because they allow functions to preserve state, reuse configuration, and manage access to variables.
Creating a Counter
A counter is one of the most common examples of a closure.
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
Output:
1
2
3
The closure remembers count between function calls. This makes it useful for tracking clicks, attempts, or other values that change over time.
Creating Configurable Functions
Closures allow you to create functions that remember configuration values.
function createDiscount(discount) {
return function (price) {
return price - (price * discount) / 100;
};
}
const tenPercentDiscount = createDiscount(10);
const twentyPercentDiscount = createDiscount(20);
console.log(tenPercentDiscount(1000));
console.log(twentyPercentDiscount(1000));
Output:
900
800
The first function remembers a discount of 10, while the second remembers a discount of 20.
This pattern is useful for reusable calculations, formatting functions, and other configurable behavior.
Closures in Event Handlers
Closures allow event handlers to access variables declared in their surrounding scope.
function setupButton() {
let clicks = 0;
document.querySelector("#myButton").addEventListener("click", function () {
clicks++;
console.log("Button clicks:", clicks);
});
}
setupButton();
If an HTML button with the ID myButton exists, each click increases the clicks variable and logs the updated count.
The event handler retains access to clicks through its closure even after setupButton() finishes executing.
Closures in Asynchronous Code
Closures also allow callbacks to access variables from their surrounding functions when the callback executes later.
function greetLater(name) {
setTimeout(function () {
console.log("Hello, " + name);
}, 1000);
}
greetLater("Karim");
Output after approximately one second:
Hello, Karim
The callback retains access to the name parameter through its closure. This is useful in timers, event handlers, and asynchronous operations.
Closures in Function Factories
A function factory is a function that creates and returns another function. Closures allow the returned functions to remember their individual configurations.
function createMultiplier(multiplier) {
return function (number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);
console.log(double(10));
console.log(triple(10));
console.log(quadruple(10));
Output:
20
30
40
Each generated function remembers its own multiplier value. Function factories make it easier to create reusable functions without repeating their core logic.
Common Mistakes When Using Closures
Assuming Closures Copy Variables
A closure does not necessarily copy the value of a variable when the function is created. It retains access to the variable binding, so later changes can be visible.
Creating Unnecessary Long-Lived References
Closures can keep variables and related objects reachable for longer than expected. If long-lived callbacks or references are no longer needed, releasing those references can help avoid unnecessary memory use.
Misunderstanding Closures in Loops
Closures can behave differently depending on whether a loop uses var or let.
For example:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
Output:
3
3
3
The callbacks share the same function-scoped i variable. When they execute, the loop has already finished, and i is 3.
Using let creates a separate binding for each loop iteration:
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
Output:
0
1
2
Each callback retains access to its iteration’s i binding.
Frequently Asked Questions
What is a closure in JavaScript?
A closure is a function that retains access to variables from its surrounding lexical environment, even after the outer function has completed.
Why are closures important in JavaScript?
Closures help preserve state, create private variables, build reusable functions, and access variables inside callbacks and event handlers.
Does every JavaScript function create a closure?
Every function has access to its lexical environment. The term closure is especially useful when a function retains and uses references to an outer lexical environment beyond the surrounding execution’s lifetime.
Can closures access variables after an outer function returns?
Yes. If an inner function still references variables from an outer function, JavaScript keeps the necessary lexical environment available while it remains reachable.
Are closures useful for data privacy?
Yes. Closures can hide variables from direct external access and expose controlled methods to read or update them. They provide encapsulation, but they should not be treated as a complete security mechanism for secrets.
What is the difference between scope and a closure?
Scope determines where a variable can be accessed. A closure describes a function’s continued access to its surrounding lexical environment.
Conclusion
Closures are a fundamental JavaScript concept that allows functions to retain access to variables from their surrounding lexical environments.
They are useful for preserving state, creating private data, handling asynchronous callbacks, building configurable functions, and managing event handlers.
Once you understand closures, you can write more flexible and reusable JavaScript code and better understand how functions interact with scope and variables.