JavaScript Closures Explained (With Interview Questions)
Closures in plain English: lexical scope, how closures work, common interview traps (loops + setTimeout), and practical use cases like memoization and data hiding.
On this page
A closure is one of those interview topics that sounds scary until you realize it’s just this:
A function “remembers” the variables from the place it was created.
30‑second interview answer
A closure is created when a function is defined inside another scope and retains a reference to its outer lexical environment. Even after the outer function finishes executing and pops off the call stack, the inner function can still access those variables because the JavaScript garbage collector does not free memory while reachable references remain.
Key points
- Closures come from lexical scoping (where functions are written in source code), not async timing.
- Closures hold live references to variables in the environment record, not immutable snapshots.
- Used everywhere in production: private state, factories, memoization, event listeners, and React hooks.
1) The smallest closure example
function outer() {
const name = 'Rajesh';
function inner() {
console.log(name);
}
return inner;
}
const fn = outer();
fn(); // 'Rajesh'Even though outer() completed and its execution context popped off the stack, calling fn() still accesses name.
That persistent link to the outer environment record is the closure.
2) What closures are NOT
Closures are not:
- a special syntax or keyword
- exclusive to asynchronous code
- exclusive to React hooks
Closures are an inherent feature of JavaScript because:
- Lexical scoping determines identifier resolution at author time.
- Functions are first-class citizens that can be passed as arguments, assigned to variables, and returned from other functions.
3) Classic interview question: “Why does this print 3, 3, 3?”
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}Why it happens:
varis function-scoped (or globally scoped), not block-scoped.- There is only one shared binding for
iacross all iterations. - When the synchronous loop completes,
ihas incremented to3. - When the macrotask queue empties the timer callbacks, all 3 functions reference the identical binding
i, logging3,3,3.
Fix 1: Use let (Modern ES6)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Output: 0, 1, 2Specification detail: In the ECMAScript spec, each iteration of a for loop with a let declaration creates a brand-new Lexical Environment Record, giving each callback its own closed-over copy of i.
Fix 2: IIFE (Immediately Invoked Function Expression)
for (var i = 0; i < 3; i++) {
(function (x) {
setTimeout(() => console.log(x), 0);
})(i);
}
// Output: 0, 1, 2The IIFE creates a new function execution context on each iteration, capturing the current value of i as the parameter x.
4) Production use cases (what senior interviewers look for)
A) Data hiding / encapsulation
function createCounter(initialValue = 0) {
let count = initialValue; // Completely private
return {
increment() {
count += 1;
return count;
},
decrement() {
count -= 1;
return count;
},
getValue() {
return count;
},
};
}
const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.getValue()); // 11
console.log(counter.count); // undefined (cannot be tampered with directly)B) Memoization & Cache Isolation
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}5) Memory leaks and accidental retention
Because closures keep outer variables reachable, holding onto an inner function prevents garbage collection of everything in its scope chain.
The circular closure trap:
function attachHeavyListener() {
const hugeData = new Uint8Array(10_000_000); // 10MB
const element = document.getElementById('submit-btn');
element.addEventListener('click', () => {
// Only needs a small flag, but closes over hugeData's entire scope
console.log('Button clicked');
});
}Fix: Nullify large objects or scope them outside the closure if the event listener remains active for the lifetime of the document.
6) Interactive interview practice
Question: Predict the output of this snippet:
function buildList() {
const result = [];
for (var i = 0; i < 3; i++) {
result.push(function () {
return i * 2;
});
}
return result;
}
const list = buildList();
console.log(list[0]());
console.log(list[1]());
console.log(list[2]());Output:
6
6
6Step-by-step trace:
var icreates a single variable bound to thebuildListfunction scope.- The loop runs 3 times, pushing 3 functions into
result. None of them execute yet. - The loop terminates when
ireaches3. buildListreturns the array of functions.- When
list[0]()is called, it resolvesithrough its lexical scope.iis currently3, so3 * 2 = 6. list[1]()andlist[2]()resolve the samei, returning6as well.
7) Rapid-fire interview Q&A
Q1: What is a closure in one sentence?
- A function bundled together with references to its surrounding lexical environment.
Q2: Do closures copy values or reference them?
- Closures keep live references to variable bindings in memory; modifying the variable will reflect when the closure reads it.
Q3: How do closures relate to React hooks?
- Hooks like
useState,useEffect, anduseCallbackrely on closures to preserve state between render passes. Stale closures occur when an effect or callback omits a state dependency, locking into an older render's variable.
Official specifications & references
- ECMA-262: Lexical Environments Specification
- MDN Web Docs: Closures
- V8 Dev Blog: Trash talk: the Orinoco garbage collector
Written by Rajesh Dhiman at Eunix Tech for the Frontend Interview preparation track.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
Written by Rajesh Dhiman
Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.