Q: Can you explain what a closure is in JavaScript? And as a follow-up, what would this code log and why? How would you fix it to log 0, 1, 2?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
Answer:
A Closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
In simpler terms, a closure gives a function access to its outer scope, even after the outer function has returned. In JavaScript, closures are created every time a function is created, at function creation time.
How it Works
When a function executes, it uses a scope chain to look up variables. If a function returns another function inside of it, that inner function maintains a "backpack" (a hidden [[Environment]] reference) containing all the variables it needs from its parent's scope.
function makeGreeting(greeting) {
// `greeting` is in the outer lexical scope
return function(name) {
// This inner function forms a closure
console.log(`${greeting}, ${name}!`);
}
}
const sayHello = makeGreeting("Hello");
const sayHowdy = makeGreeting("Howdy");
// `makeGreeting` has already finished executing, but `sayHello`
// still remembers the `greeting` variable ("Hello")!
sayHello("Abhay"); // Output: Hello, Abhay!
sayHowdy("Abhay"); // Output: Howdy, Abhay!
Why are Closures Useful?
-
Data Privacy / Encapsulation: JavaScript did not historically have access modifiers like
private. Closures allow you to create private variables that cannot be accessed from the outside.function createCounter() { let count = 0; // Private variable return { increment: () => ++count, getCount: () => count }; } const counter = createCounter(); counter.increment(); console.log(counter.getCount()); // 1 console.log(counter.count); // undefined (cannot access directly!) -
Function Factories: Creating partially applied functions (like
makeGreetingabove). -
Memoization / Caching: Keeping a private cache object to store expensive calculation results.
-
Callbacks & Event Handlers: When attaching an event listener, you often use variables from the outer scope inside the callback. The event listener forms a closure to remember those variables when the event actually fires.
Classic Interview "Gotcha"
A common interview question involves closures inside a for loop using var vs let:
// Using var (Function Scoped)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Output: 3, 3, 3
// Because `var` is function-scoped, there is only one `i` variable shared
// by all closures. By the time the timeout runs, the loop has finished and `i` is 3.
// Using let (Block Scoped)
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 1000);
}
// Output: 0, 1, 2
// Because `let` is block-scoped, a new `j` is created for every single iteration.
// Each closure gets its own independent copy.