π¨ JavaScript Β· Topic 13 of 20
Closures β JavaScript Questions with Answers
30 practice questions on Closures, each with the correct answer and a clear explanation of why it is right. Read through them as revision notes, or take this topic as an interactive quiz to test yourself.
0 easy Β· 14 medium Β· 16 hard Β· Part of JavaScript: The language of the web - basics to advanced, 20 topics.
Medium questions (14)
Q1. medium
What is a closure?
function outer() {
const msg = "hi";
return () => msg; // closure over msg
}
console.log(outer()()); // "hi"
- A A function that remembers variables from the scope where it was CREATED, even after that scope has finished
- B A function that closes the browser
- C A way to end a loop
- D A private class method
Answer: A β A function that remembers variables from the scope where it was CREATED, even after that scope has finished
When an inner function references an outer variable and outlives the outer call, JavaScript keeps that variable alive for it. Closures are the foundation of callbacks, modules and React hooks.
Lexical scopeFunctions
Q2. medium
What is the output?
function outer() {
let x = 10;
return function() { return x; };
}
const f = outer();
console.log(f());
- A 10 - the closure keeps x alive after outer() returned
- B undefined
- C ReferenceError
- D 0
Answer: A β 10 - the closure keeps x alive after outer() returned
Normally locals vanish when a function returns, but because the returned function still references x, it is retained in memory.
ClosuresMemory
Q3. medium
What does this counter demonstrate?
function makeCounter() {
let count = 0;
return () => ++count;
}
const c = makeCounter();
console.log(c(), c(), c());
- A 1 2 3 - the closure keeps private state between calls
- B 1 1 1
- C 0 1 2
- D undefined
Answer: A β 1 2 3 - the closure keeps private state between calls
count lives in the closure, not the global scope, and persists across calls - giving encapsulated, tamper-proof state.
ClosuresState
Q6. medium
How does changing var to let fix the loop-closure problem?
for (let i = 0; i < 3; i++) setTimeout(() => console.log(i));
// 0 1 2
- A let creates a NEW binding per iteration, so each callback closes over its own copy
- B let delays the timers
- C let makes the loop synchronous
- D It does not fix it
Answer: A β let creates a NEW binding per iteration, so each callback closes over its own copy
The specification re-binds a let loop variable each iteration, giving three separate closures that print 0 1 2.
letClosures
Q8. medium
What is the module pattern using closures?
const bank = (function() {
let balance = 0; // private
return {
deposit(n) { balance += n; },
getBalance() { return balance; }
};
})();
bank.deposit(100);
console.log(bank.balance); // undefined - private!
- A An IIFE that keeps variables private and returns only the public methods
- B Importing files
- C A class
- D A loop pattern
Answer: A β An IIFE that keeps variables private and returns only the public methods
Before ES modules, this was the standard way to get privacy: internal state lives in the closure and only the returned object is reachable.
Module patternEncapsulation
Q9. medium
How do closures create 'private' variables?
- A The variable is only reachable through the functions that closed over it - nothing outside can touch it
- B By using the private keyword
- C By using const
- D They cannot
Answer: A β The variable is only reachable through the functions that closed over it - nothing outside can touch it
There is no way to reach a closure variable from outside except through the exposed functions - real encapsulation without any special syntax.
EncapsulationPrivacy
Q12. medium
Why do event handlers commonly rely on closures?
function setup(id) {
btn.addEventListener("click", () => console.log(id)); // remembers id
}
- A The handler runs long after setup, but still needs the variables from when it was created
- B Events require the closure keyword
- C Closures make events faster
- D They do not use closures
Answer: A β The handler runs long after setup, but still needs the variables from when it was created
A click may happen minutes later; the callback still sees the id, element or config captured at registration time - because of the closure.
EventsCallbacks
Q16. medium
Do closures keep variables in memory longer than usual?
- A Yes - captured variables cannot be garbage collected while the closure lives (a possible leak if careless)
- B No, memory is freed immediately
- C Closures use no memory
- D Only in old browsers
Answer: A β Yes - captured variables cannot be garbage collected while the closure lives (a possible leak if careless)
This is normally fine and intentional, but holding closures that capture large DOM nodes or datasets (e.g. never-removed listeners) can cause memory leaks.
MemoryGarbage collection
Q18. medium
Can a closure MODIFY the variable it captured?
- A Yes - it holds a live reference, so it can read and write
- B No, captured variables are read-only
- C Only with const
- D Only inside loops
Answer: A β Yes - it holds a live reference, so it can read and write
That is precisely how counters work: the inner function increments the outer variable, and the change persists for the next call.
MutationState
Q20. medium
Are closures created for EVERY function, or only special ones?
- A Every function forms a closure over its enclosing scope - we only 'notice' when it outlives that scope
- B Only functions with the closure keyword
- C Only arrow functions
- D Only returned functions
Answer: A β Every function forms a closure over its enclosing scope - we only 'notice' when it outlives that scope
Closure is inherent to lexical scoping. The term is usually used when a function is passed around or returned, keeping outer variables alive.
Lexical scope
Q23. medium
What does the returned function 'close over' here?
function greeter(greeting) {
return name => `${greeting}, ${name}!`;
}
const hi = greeter("Hi");
console.log(hi("Ana")); // "Hi, Ana!"
- A The greeting parameter
- B The name parameter
- C Nothing
- D The whole global scope
Answer: A β The greeting parameter
greeting belongs to the outer call and is captured; name is the inner function's own parameter, supplied at call time.
ClosuresParameters
Q25. medium
Which everyday feature is powered by closures?
- A setTimeout callbacks, event handlers, array method callbacks and React hooks
- B Only recursion
- C Only classes
- D None - closures are theoretical
Answer: A β setTimeout callbacks, event handlers, array method callbacks and React hooks
Any callback that uses variables from where it was written is a closure. You have been using them since your first setTimeout.
CallbacksReal-world
Q27. medium
How can a closure cause a memory leak in the DOM?
- A A listener that captures a large element and is never removed keeps it in memory after removal from the page
- B Closures always leak
- C By using const
- D Closures cannot leak
Answer: A β A listener that captures a large element and is never removed keeps it in memory after removal from the page
The captured reference blocks garbage collection. Call removeEventListener when cleaning up (React does this in the effect cleanup function).
Memory leaksEvents
Q29. medium
In one sentence, why do closures matter?
- A They let functions carry private, persistent state - enabling callbacks, factories, modules and encapsulation
- B They make code shorter
- C They speed up loops
- D They are required by the browser
Answer: A β They let functions carry private, persistent state - enabling callbacks, factories, modules and encapsulation
Closures are what make JavaScript's functional patterns possible: data privacy without classes, and callbacks that remember their context.
Summary
Hard questions (16)
Q4. hard
What is the output?
const a = makeCounter();
const b = makeCounter();
console.log(a(), a(), b());
- A 1 2 1 - each call to makeCounter creates a SEPARATE closure
- B 1 2 3
- C 1 1 1
- D 3 3 3
Answer: A β 1 2 1 - each call to makeCounter creates a SEPARATE closure
Every invocation creates a fresh scope with its own count. a and b are completely independent - like two instances of a class.
ClosuresIndependence
Q5. hard
What is the output?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
- A 3 3 3 - all closures share ONE var binding, which is 3 when the timers run
- B 0 1 2
- C 0 0 0
- D undefined x3
Answer: A β 3 3 3 - all closures share ONE var binding, which is 3 when the timers run
The loop finishes before any timer fires, and var has a single function-scoped binding. Every callback reads the same final value.
- The most famous JavaScript closure question.
varEvent loopClosures
Q7. hard
How would you fix the var loop WITHOUT using let?
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j));
})(i);
} // 0 1 2
- A Wrap the body in an IIFE that receives i as a parameter
- B Add a return statement
- C Use a while loop
- D It cannot be fixed
Answer: A β Wrap the body in an IIFE that receives i as a parameter
The classic pre-ES6 solution: an immediately invoked function creates a new scope per iteration, capturing the current value as its parameter.
IIFEClosures
Q10. hard
What is the output?
function multiplier(factor) {
return n => n * factor;
}
const triple = multiplier(3);
console.log(triple(5));
- A 15 - the closure remembers factor = 3
- B 5
- C 3
- D undefined
Answer: A β 15 - the closure remembers factor = 3
multiplier is a function factory: it bakes a value into the returned function. This pattern (partial application) is used constantly in functional JavaScript.
Function factoriesCurrying
Q11. hard
What is currying?
const add = a => b => c => a + b + c;
console.log(add(1)(2)(3)); // 6
const add5 = add(5); // reusable
- A Turning a multi-argument function into a chain of single-argument functions returning functions
- B Sorting an array
- C Caching results
- D An error-handling pattern
Answer: A β Turning a multi-argument function into a chain of single-argument functions returning functions
Currying relies on closures to remember earlier arguments: add(1)(2)(3). It enables reusable specialised functions.
CurryingClosures
Q13. hard
What is the output?
let x = 1;
function show() { console.log(x); }
x = 99;
show();
- A 99 - closures capture the VARIABLE, not a snapshot of its value
- B 1
- C undefined
- D ReferenceError
Answer: A β 99 - closures capture the VARIABLE, not a snapshot of its value
A closure holds a live reference to the binding, so it sees the latest value. This is exactly why the var-loop prints 3 3 3.
Live bindingClosures
Q14. hard
What is memoization, and how do closures enable it?
function memoize(fn) {
const cache = {};
return n => {
if (n in cache) return cache[n];
return (cache[n] = fn(n));
};
}
- A Caching results in a closure-held object so repeated calls with the same input return instantly
- B Writing memos in comments
- C Freeing memory
- D Sorting cached data
Answer: A β Caching results in a closure-held object so repeated calls with the same input return instantly
The cache lives in the closure - private, persistent between calls, and invisible to callers.
MemoizationCaching
Q15. hard
What does this debounce function use closures for?
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
input.addEventListener("input", debounce(search, 300));
- A To remember the pending timer id between calls, so rapid calls cancel the previous one
- B To speed up the function
- C To count calls
- D Nothing - closures are unused
Answer: A β To remember the pending timer id between calls, so rapid calls cancel the previous one
timer persists across invocations in the closure. Debouncing is essential for search-as-you-type and resize handlers.
DebounceReal-world
Q17. hard
What is the output?
function f() {
const fns = [];
for (let i = 0; i < 3; i++) fns.push(() => i);
return fns.map(fn => fn());
}
console.log(f());
- A [0, 1, 2] - let gives each closure its own i
- B [3, 3, 3]
- C [0, 0, 0]
- D [undefined x3]
Answer: A β [0, 1, 2] - let gives each closure its own i
Because let re-binds per iteration, the three stored functions capture different values. With var the answer would be [3, 3, 3].
Closureslet
Q19. hard
What is the output?
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns.map(f => f()));
- A [3, 3, 3] - all three share the single var binding
- B [0, 1, 2]
- C [0, 0, 0]
- D Error
Answer: A β [3, 3, 3] - all three share the single var binding
var creates one binding for the whole function; after the loop it holds 3, and every closure reads that same variable.
varClosures
Q21. hard
What does this once() helper do?
function once(fn) {
let called = false, result;
return (...a) => {
if (!called) { called = true; result = fn(...a); }
return result;
};
}
- A Ensures fn runs only the FIRST time, caching and returning that result afterwards
- B Runs fn once per second
- C Deletes fn after use
- D Runs fn in a loop
Answer: A β Ensures fn runs only the FIRST time, caching and returning that result afterwards
The closure remembers called and result. Useful for one-time initialisation, single-submit buttons and lazy setup.
ClosuresPatterns
Q22. hard
Why is this a common React-hooks pitfall (stale closure)?
useEffect(() => {
const id = setInterval(() => setCount(count + 1), 1000);
}, []);
- A The effect captured count from the FIRST render, so it always adds 1 to that stale value
- B setInterval is not allowed
- C The syntax is wrong
- D It works correctly
Answer: A β The effect captured count from the FIRST render, so it always adds 1 to that stale value
With an empty dependency array the callback closes over the initial count forever. The fix is the functional updater: setCount(c => c + 1), which does not depend on the captured value.
ReactStale closures
Q24. hard
What is the output?
let fn;
{
let secret = "hidden";
fn = () => secret;
}
console.log(fn());
- A "hidden" - the block ended, but the closure keeps secret alive
- B ReferenceError
- C undefined
- D null
Answer: A β "hidden" - the block ended, but the closure keeps secret alive
Closures work over any scope, including plain blocks. secret is unreachable by name but preserved for fn.
Block scopeClosures
Q26. hard
What is the output?
function counter() {
let n = 0;
return { inc: () => ++n, get: () => n };
}
const c = counter();
c.inc(); c.inc();
console.log(c.get(), c.n);
- A 2 undefined - both methods share the closure, but n is not a property
- B 2 2
- C 0 0
- D undefined undefined
Answer: A β 2 undefined - both methods share the closure, but n is not a property
Several functions from the same scope share one closure, which is how the module pattern works. n stays private, so c.n is undefined.
Shared closuresPrivacy
Q28. hard
What is the output?
const add = (a) => (b) => a + b;
console.log(add(2)(3), add(2)(10));
- A 5 12 - each call to add(2) creates a fresh closure
- B 5 5
- C 12 12
- D undefined
Answer: A β 5 12 - each call to add(2) creates a fresh closure
add(2) returns a new function each time, all remembering a = 2 but applied to different b values.
CurryingClosures
Q30. hard
What is the output?
function f() {
var x = 1;
{ var x = 2; }
return x;
}
console.log(f());
- A 2 - var ignores the block, so both declarations are the SAME variable
- B 1
- C undefined
- D SyntaxError
Answer: A β 2 - var ignores the block, so both declarations are the SAME variable
With var there is one function-scoped x that gets overwritten. With let, the inner one would be a separate block-scoped variable and the function would return 1.
varBlock scopeShadowing