Difficulty: Intermediate
To understand closures, you first need to understand lexical scoping. Lexical scoping means that the accessibility of variables is determined by their physical location in the source code. When JavaScript encounters a variable reference, it looks up the scope chain starting from the innermost scope outward until it finds the variable. This lookup is based on where functions are defined (written), not where they are called. This is why it is called 'lexical' -- it depends on the structure of the source text.
A closure is created when a function retains access to variables from its outer (enclosing) scope even after the outer function has finished executing and its execution context has been removed from the call stack. Every function in JavaScript forms a closure over its surrounding lexical environment. However, the term 'closure' is most meaningful when an inner function references variables from an outer function that has already returned. The inner function 'closes over' those variables, keeping them alive in memory.
One crucial detail that trips up many developers is that closures capture references to variables, not their values at the time the closure is created. This means if the outer variable changes after the closure is created, the closure will see the updated value. This behavior is both powerful and a common source of bugs, especially when closures are created inside loops using `var` (which is function-scoped rather than block-scoped).
Closures have several important practical applications. They enable data privacy by creating variables that cannot be accessed from outside a function, effectively simulating private fields. Factory functions leverage closures to produce customized functions with pre-configured state. Event handlers and timers naturally create closures over the variables in scope when they are defined.
The module pattern, which was the standard way to organize JavaScript code before ES6 modules, relies entirely on closures. An IIFE (Immediately Invoked Function Expression) creates a private scope, and the returned object contains methods that close over the private variables. Even with modern ES6 modules, closures remain fundamental to patterns like memoization, currying, and state management in frameworks like React (where hooks like useState rely on closures internally).
The classic closure-in-a-loop problem illustrates why understanding closures matters. When using `var` in a for loop, all iterations share the same variable binding. Closures created inside the loop all reference the same variable, so they all see its final value. Using `let` (which is block-scoped) solves this because each iteration gets its own binding. This is one of the most frequently asked closure questions in interviews.
function createCounter() {
let count = 0; // This variable is "closed over"
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.increment()); // 3
console.log(counter.decrement()); // 2
console.log(counter.getCount()); // 2
// count is not accessible directly
console.log(typeof count); // undefined
After createCounter() finishes executing, the count variable would normally be garbage collected. But because the returned object's methods reference count, it stays alive in memory. This is a closure in action -- the inner functions close over the outer variable.
function createFunctions() {
let value = 1;
function getValue() {
return value;
}
function setValue(newVal) {
value = newVal;
}
return { getValue, setValue };
}
const obj = createFunctions();
console.log(obj.getValue()); // 1
obj.setValue(42);
console.log(obj.getValue()); // 42
// Both functions share the SAME variable reference
obj.setValue(100);
console.log(obj.getValue()); // 100
Both getValue and setValue close over the same variable `value`. When setValue modifies it, getValue sees the change because closures capture references to variables, not snapshots of their values.
// Problem with var (function-scoped)
function createHandlersVar() {
const handlers = [];
for (var i = 0; i < 3; i++) {
handlers.push(function() {
return i;
});
}
return handlers;
}
const varHandlers = createHandlersVar();
console.log(varHandlers[0]()); // 3 (not 0!)
console.log(varHandlers[1]()); // 3 (not 1!)
console.log(varHandlers[2]()); // 3 (not 2!)
// Fix with let (block-scoped)
function createHandlersLet() {
const handlers = [];
for (let i = 0; i < 3; i++) {
handlers.push(function() {
return i;
});
}
return handlers;
}
const letHandlers = createHandlersLet();
console.log(letHandlers[0]()); // 0
console.log(letHandlers[1]()); // 1
console.log(letHandlers[2]()); // 2
With var, there is only one `i` variable shared across all iterations. All closures reference the same `i`, which is 3 after the loop ends. With let, each iteration gets its own `i` binding, so each closure captures a different value.
const BankAccount = (function() {
// Private variables
let balance = 0;
const transactionLog = [];
// Private function
function logTransaction(type, amount) {
transactionLog.push({
type,
amount,
balance,
timestamp: new Date().toISOString()
});
}
// Public API (returned object)
return {
deposit(amount) {
if (amount <= 0) throw new Error('Amount must be positive');
balance += amount;
logTransaction('deposit', amount);
return balance;
},
withdraw(amount) {
if (amount > balance) throw new Error('Insufficient funds');
balance -= amount;
logTransaction('withdrawal', amount);
return balance;
},
getBalance() {
return balance;
},
getHistory() {
return [...transactionLog]; // Return copy, not reference
}
};
})();
console.log(BankAccount.deposit(1000));
console.log(BankAccount.withdraw(250));
console.log(BankAccount.getBalance());
console.log(BankAccount.getHistory().length);
The IIFE creates a private scope. The balance and transactionLog variables are completely hidden from the outside world. Only the returned methods can access them. This was the standard module pattern before ES6 modules.
Lexical Scoping, Closures, Data Privacy, Factory Functions, Module Pattern, Closure in Loops