Closures
What is a Closure?
A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned. Closures are created every time a function is created.
Try Closures
Code Editor
Console Output
Click "Run" to execute your code...
Example 1: Basic Closure
function outer() {
const outerVar = 'I am from outer';
function inner() {
console.log(outerVar); // Can access outerVar
}
return inner;
}
const myFunction = outer();
myFunction(); // I am from outer
// Even though outer() has finished executing,
// inner() still has access to outerVar!
// Another example
function createGreeting(greeting) {
return function(name) {
console.log(`${greeting}, ${name}!`);
};
}
const sayHello = createGreeting('Hello');
const sayHi = createGreeting('Hi');
sayHello('John'); // Hello, John!
sayHi('Jane'); // Hi, Jane!Example 2: Counter with Closure
function createCounter() {
let count = 0; // Private variable
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
},
reset: function() {
count = 0;
return count;
}
};
}
const counter1 = createCounter();
console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter1.increment()); // 3
console.log(counter1.getCount()); // 3
console.log(counter1.decrement()); // 2
console.log(counter1.reset()); // 0
// Each counter has its own private count
const counter2 = createCounter();
console.log(counter2.increment()); // 1
console.log(counter1.getCount()); // 0 (not affected!)Example 3: Closure in Loops
// Problem: var doesn't create closure per iteration
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3
}, 1000);
}
// Solution 1: Use let (creates new binding per iteration)
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 1000);
}
// Solution 2: IIFE creates closure
for (var k = 0; k < 3; k++) {
(function(x) {
setTimeout(function() {
console.log(x); // 0, 1, 2
}, 1000);
})(k);
}
// Solution 3: Function factory
function createLogger(index) {
return function() {
console.log(index);
};
}
for (var m = 0; m < 3; m++) {
setTimeout(createLogger(m), 1000); // 0, 1, 2
}Example 4: Private Variables and Methods
function BankAccount(initialBalance) {
// Private variables
let balance = initialBalance;
let transactions = [];
// Private method
function recordTransaction(type, amount) {
transactions.push({
type,
amount,
date: new Date(),
balance: balance
});
}
// Public methods (closure over private variables)
return {
deposit: function(amount) {
if (amount > 0) {
balance += amount;
recordTransaction('deposit', amount);
return `Deposited: $${amount}. New balance: $${balance}`;
}
return 'Invalid amount';
},
withdraw: function(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
recordTransaction('withdrawal', amount);
return `Withdrew: $${amount}. New balance: $${balance}`;
}
return 'Invalid amount or insufficient funds';
},
getBalance: function() {
return balance;
},
getTransactions: function() {
return [...transactions]; // Return copy, not reference
}
};
}
const myAccount = new BankAccount(1000);
console.log(myAccount.deposit(500)); // Deposited: $500. New balance: $1500
console.log(myAccount.withdraw(200)); // Withdrew: $200. New balance: $1300
console.log(myAccount.getBalance()); // 1300
// console.log(myAccount.balance); // undefined (private!)
console.log(myAccount.getTransactions().length); // 2Example 5: Function Factories
// Multiplier factory
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(quadruple(5)); // 20
// String formatter factory
function createFormatter(prefix, suffix) {
return function(str) {
return `${prefix}${str}${suffix}`;
};
}
const addQuotes = createFormatter('"', '"');
const addParens = createFormatter('(', ')');
const addBrackets = createFormatter('[', ']');
console.log(addQuotes('Hello')); // "Hello"
console.log(addParens('Hello')); // (Hello)
console.log(addBrackets('Hello')); // [Hello]
// API caller factory
function createAPI(baseURL) {
return {
get: function(endpoint) {
return `GET ${baseURL}${endpoint}`;
},
post: function(endpoint, data) {
return `POST ${baseURL}${endpoint} with ${JSON.stringify(data)}`;
}
};
}
const userAPI = createAPI('https://api.example.com/users');
const productAPI = createAPI('https://api.example.com/products');
console.log(userAPI.get('/123')); // GET https://api.example.com/users/123
console.log(productAPI.post('/new', {name: 'Widget'})); // POST with dataKey Points
- Closures allow functions to access variables from outer scope
- Inner function maintains reference to outer scope, even after outer function returns
- Closures are useful for data privacy and encapsulation
- Every function in JavaScript is a closure
- Closures can cause memory leaks if not managed properly
- Common uses: callbacks, event handlers, private variables, function factories