JavaScript Closures Interview Questions
Short answer
A closure is a function together with the variables it captured from the scope where it was defined, kept alive after that scope has returned. It is not a feature you switch on — it is the natural result of lexical scope, and every JavaScript function is one.
| Pattern | What the closure holds | Why it needs one |
|---|---|---|
| Module pattern | Private variables | Nothing outside can reach them |
| Function factory | The configuration argument | Each returned function remembers its own |
| Memoisation | The cache | Survives between calls without a global |
| debounce / throttle | The timer id | Each instance needs its own |
| Event handlers | Surrounding state | The handler runs long after setup |
| React hooks | Props and state of that render | Each render captures its own copy |
What is a closure and why does it exist?
A closure is a function bundled with the environment it was created in. When a function is defined, it keeps a reference to the scope around it — and that scope stays alive for as long as the function does, even after the outer function has returned. This is not a special mechanism added for convenience; it falls directly out of lexical scoping, which means a function's variable lookup is decided by where it was written, not by where it is called.
1function counter() {
2 let count = 0; // local to this call
3 return function () {
4 count += 1; // still reachable
5 return count;
6 };
7}
8
9const a = counter();
10console.log(a(), a(), a());
11// → 1 2 3 `count` outlived counter() returning
12
13const b = counter();
14console.log(b());
15// → 1 a separate call, a separate `count`The second half of that example is the part worth stressing. Every call to the outer function creates a fresh scope, so `a` and `b` close over completely independent variables. That is why closures work as a factory for instances of behaviour, and why they are the mechanism behind almost every hook and utility in the table above.
Do closures capture values or variables?
Variables, not values — and this single fact explains almost every closure puzzle. A closure holds a live reference to the binding, so if the variable changes after the closure was created, the closure sees the new value. People who believe closures snapshot values get every loop question wrong, and it is precisely why the question is asked.
1let msg = "first";
2const show = () => console.log(msg);
3
4show();
5// → first
6
7msg = "second";
8show();
9// → second the closure re-reads the variable, it did not copy it
10
11// Contrast with a parameter, which IS a copy taken at call time:
12const showFixed = ((m) => () => console.log(m))(msg);
13msg = "third";
14showFixed();
15// → second captured when the IIFE ranWhy does a var loop with setTimeout print the same number?
This is the most-asked closure question in existence. A `var` declaration is function-scoped, so the loop creates exactly one binding that every iteration shares. All three callbacks close over that same variable, and by the time the timers fire the loop has finished and the variable holds its final value. `let` fixes it because a `for` loop with `let` creates a fresh binding per iteration and copies the current value into it.
1for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0);
2// → 3
3// → 3
4// → 3 one shared `i`, read after the loop ended
5
6for (let i = 0; i < 3; i++) setTimeout(() => console.log(i), 0);
7// → 0
8// → 1
9// → 2 a new binding each iteration
10
11// Before let existed, an IIFE created the per-iteration scope:
12for (var i = 0; i < 3; i++) {
13 (function (j) { setTimeout(() => console.log(j), 0); })(i);
14}
15// → 0 1 2 `j` is a parameter, so it is a copyBeing able to explain the IIFE version matters more than it looks. It proves you understand that the fix is about creating a new scope per iteration rather than about `let` being magic — and it is exactly the follow-up an interviewer reaches for when a candidate answers the first part too fluently.
What is the module pattern?
Before ES modules, closures were the only way to get privacy in JavaScript. An IIFE creates a scope, you declare state inside it, and you return only the functions you want to expose. Nothing outside can read or write the internal variables because there is no reference to them — this is genuine privacy enforced by scope, not a naming convention.
1const store = (function () {
2 let items = []; // unreachable from outside
3
4 return {
5 add(x) { items.push(x); return this; },
6 size() { return items.length; },
7 };
8})();
9
10store.add("a").add("b");
11console.log(store.size());
12// → 2
13console.log(store.items);
14// → undefined there is no way to reach it
15
16// The modern equivalents:
17class Store { #items = []; size() { return this.#items.length; } }
18// → # private fields, enforced by the language
19// …or simply an ES module: anything not exported is privateThere is a reason the module pattern still deserves a mention even though ES modules replaced it. Bundlers wrap your code in functions, so the closures are still there underneath; and any time you write a factory that returns an object of methods sharing some private state, you are writing the module pattern whether or not you call it that. Recognising the shape in unfamiliar code is more useful than remembering the name.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299What is currying and partial application?
Currying transforms a function of several arguments into a chain of single-argument functions, each returning the next. Partial application is the looser cousin: fixing some arguments now and supplying the rest later. Both are closures — each returned function remembers the arguments collected so far, which is the only reason the chain works.
1const add = (a) => (b) => (c) => a + b + c;
2console.log(add(1)(2)(3));
3// → 6
4
5const add5 = add(5);
6console.log(add5(10)(1));
7// → 16 add5 closed over a = 5
8
9// Generic curry — a very common whiteboard prompt
10function curry(fn) {
11 return function curried(...args) {
12 if (args.length >= fn.length) return fn.apply(this, args);
13 return (...rest) => curried.apply(this, [...args, ...rest]);
14 };
15}
16
17const volume = curry((l, w, h) => l * w * h);
18console.log(volume(2)(3)(4), volume(2, 3)(4), volume(2, 3, 4));
19// → 24 24 24The detail that makes the generic version work is `fn.length`, which reports how many declared parameters a function has. That is also its limitation: a function using rest parameters or defaults reports a shorter length, so the curried version fires early. Volunteering that limitation is a good way to show you understand the code rather than having memorised it.
How do closures enable memoisation?
A memoised function needs somewhere to keep results between calls that is not a global. A closure is exactly that: declare the cache in the outer function, return the inner one, and the cache lives as long as the returned function does. This is the same shape as the module pattern applied to performance.
1function memoize(fn) {
2 const cache = new Map(); // private to this memoized function
3 return function (...args) {
4 const key = JSON.stringify(args);
5 if (cache.has(key)) return cache.get(key);
6 const result = fn.apply(this, args);
7 cache.set(key, result);
8 return result;
9 };
10}
11
12let calls = 0;
13const slowSquare = (n) => { calls++; return n * n; };
14const fast = memoize(slowSquare);
15
16console.log(fast(4), fast(4), fast(4), calls);
17// → 16 16 16 1 computed once, served three timesMemoisation is also the cleanest illustration of why a closure beats a global for this job. A module-level cache would be shared by every consumer, so two callers with different needs would fight over the same entries and tests would leak state into one another. Putting the cache inside the closure means each memoised function owns exactly one, created at the moment it was built and discarded with it.
How do closures cause memory leaks?
Garbage collection frees anything unreachable. A closure keeps its captured scope reachable, so as long as the function exists, everything it closed over survives — including large objects and DOM nodes you thought were gone. The classic leak is an event listener that captures a big structure and is never removed; the node is detached from the document but the listener still references it, so neither can be collected.
1function attach() {
2 const bigData = new Array(1_000_000).fill("x");
3 const node = document.querySelector("#panel");
4
5 node.addEventListener("click", () => {
6 console.log(bigData.length); // captures bigData AND node
7 });
8 // → removing #panel from the DOM does NOT free either one
9}
10
11// Fixed: keep the reference, remove it when done
12function attachSafely() {
13 const bigData = new Array(1_000_000).fill("x");
14 const node = document.querySelector("#panel");
15 const onClick = () => console.log(bigData.length);
16
17 node.addEventListener("click", onClick);
18 return () => node.removeEventListener("click", onClick);
19 // → call the returned function and both become collectable
20}That returned cleanup function is exactly what a React effect returns, which is worth saying out loud if the conversation is React-adjacent. It is also the reason `AbortController` is convenient for listeners: pass a signal to `addEventListener` and one `abort()` call removes every listener registered with it, which is far harder to get wrong than tracking each handler by hand.
Detached DOM nodes are worth naming explicitly, because that is the phrase you will see in Chrome DevTools. Take a heap snapshot, filter for "Detached", and anything listed is a node removed from the document that something still holds a reference to — almost always a closure inside a listener, a timer, or an observer nobody disconnected. Knowing where to look is half the answer to "how would you debug a memory leak".
What is a stale closure in React?
Every render of a function component creates new props, new state and new functions — so a callback defined in one render closes over that render's values forever. Usually that is exactly what you want. It becomes a bug when the callback outlives the render, which is what happens with an interval created once on mount: it keeps calling the version of the function that captured the initial state.
1function Broken() {
2 const [count, setCount] = useState(0);
3
4 useEffect(() => {
5 const id = setInterval(() => setCount(count + 1), 1000);
6 return () => clearInterval(id);
7 }, []); // captured count = 0 forever
8 // → 1, 1, 1, 1…
9
10 return <p>{count}</p>;
11}
12
13// Fix 1 — the updater form never reads the captured value
14setInterval(() => setCount(c => c + 1), 1000);
15// → 1, 2, 3, 4…
16
17// Fix 2 — a ref always holds the latest value
18const latest = useRef(count);
19useEffect(() => { latest.current = count; });
20
21// Fix 3 — add the dependency and let the effect re-subscribe
22useEffect(() => { /* … */ }, [count]);The reason to prefer the updater form is that it removes the dependency entirely rather than working around it. Fix 3 is correct but recreates the interval every second, which resets the timing; the ref version keeps the interval stable but adds a mutable value you have to remember to update. Explaining that trade-off is a genuinely senior answer to what looks like a beginner bug.
How do you explain a closure in an interview?
Give the one-line definition, then immediately show the counter. The definition alone sounds memorised; the example proves it. Then connect it to something the interviewer's team actually uses — private state, a debounce, or a React hook — because the question behind the question is whether you recognise closures in code rather than only in exercises.
- "A closure is a function plus the variables it captured from where it was defined."
- Show the counter factory — two independent counters from one function.
- Note that it captures the variable, not a snapshot of the value.
- Name one place it appears in real code: a debounce timer, a memo cache, or useState.
- If asked for a downside, mention the memory cost and detached DOM nodes.
Does every JavaScript function create a closure?
Technically yes — every function keeps a reference to the scope it was defined in. The word is normally reserved for cases where that matters, meaning the function outlives its defining scope. Engines also optimise: variables the inner function never references may not be retained at all.
What is the difference between scope and a closure?
Scope is the set of variables reachable at a point in the code, decided at author time by where things are written. A closure is what happens when a function carries that scope with it beyond the lifetime of the call that created it. Scope is the rule; a closure is the consequence.
Do arrow functions behave differently in closures?
For variable capture, no — they close over their surrounding scope exactly like function declarations. The difference is `this`, `arguments` and `super`, which arrow functions do not bind and instead inherit lexically. That is why an arrow callback inside a method sees the method's `this`.
How do closures relate to the module pattern and ES modules?
The module pattern used an IIFE's closure to hide state, because JavaScript had no other privacy. ES modules made that structural: anything not exported is unreachable from outside. The closure version still appears in older code and in libraries that ship a single-file bundle.
Can a closure be garbage collected?
Yes, once nothing references the function itself. The captured scope is freed at the same time. Leaks happen when something long-lived — a global cache, an event listener, a timer — keeps the function alive, which keeps everything it captured alive with it.
Frequently asked questions
- What is a closure in JavaScript?
- A function together with the variables it captured from the scope where it was defined, which stay alive as long as the function does. It follows from lexical scoping — a function's variable lookup is decided by where it was written, not where it is called — so closures are not a feature you enable, they are how the language works.
- Why are closures asked so often in interviews?
- Because they reveal whether you understand scope, and because so much real code depends on them: private state, function factories, memoisation, debounce and throttle, and every React hook that remembers a value between renders. A candidate who understands closures can reason about all of those; one who has memorised the definition cannot.
- Do closures store values or references?
- References to variables, not copies of values. If the captured variable changes after the closure was created, the closure sees the new value. This is the mechanism behind the setTimeout loop puzzle, and believing the opposite is why most people get that question wrong.
- Why does setTimeout in a for loop with var print the last value?
- var is function-scoped, so the whole loop shares one binding and every callback closes over that same variable. By the time the timers fire the loop has finished, so they all read the final value. let creates a fresh binding for each iteration, which is why switching one keyword fixes it.
- How do you fix the loop closure problem without let?
- Wrap the body in an immediately invoked function that takes the loop variable as a parameter. Parameters are copies made at call time, so each iteration gets its own. Passing the value as a third argument to setTimeout also works, since that argument is forwarded to the callback.
- What is the module pattern?
- An IIFE that declares private state and returns only the functions meant to be public. Because nothing outside holds a reference to the internal variables, they are genuinely unreachable. ES modules and class private fields have largely replaced it, but it appears throughout pre-2015 code and in single-file libraries.
- What is currying in JavaScript?
- Turning a function that takes several arguments into a chain of single-argument functions, each returning the next until enough arguments have been collected. Each link is a closure over the arguments so far. A generic curry helper using fn.length is a common whiteboard prompt.
- How do closures cause memory leaks?
- A closure keeps its captured scope reachable, so nothing it captured can be garbage collected while the function exists. The classic case is an event listener that captures a large object and a DOM node and is never removed — the node stays detached but alive. Removing the listener frees both.
- What is a stale closure in React?
- A callback holding values from an earlier render. An effect with an empty dependency array captures the state at mount, so an interval created there keeps seeing the initial value. Fix it with the setState updater form, a ref holding the latest value, or a correct dependency array.
- Does every function create a closure?
- Technically yes, since every function references the scope it was defined in. The term is normally used for cases where that reference outlives the enclosing call. Engines optimise the rest away — variables the inner function never mentions may not be retained.
- What is the difference between a closure and a callback?
- They are unrelated ideas that usually appear together. A callback is a function passed as an argument to be called later; a closure is a function that carries its defining scope. Most callbacks happen to be closures, which is why they can still see the variables around where they were written.
- Are closures slow?
- No, in any way that matters. Creating one allocates a small amount of memory for the captured scope, and engines optimise heavily by retaining only what is actually referenced. The real cost is memory retention when closures are held by something long-lived, not execution speed.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299