πŸŽ“ Velnov

🟨 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.

Key concepts in this topic

Lexical scopeFunctionsClosuresMemoryStateIndependencevarEvent loopletIIFEModule patternEncapsulationPrivacyFunction factoriesCurryingEventsCallbacksLive bindingMemoizationCachingDebounceReal-worldGarbage collectionMutation

Jump to: Medium Β· Hard

Medium questions (14)

Q1. medium

What is a closure?

function outer() {
  const msg = "hi";
  return () => msg;    // closure over msg
}
console.log(outer()());   // "hi"
  1. A A function that remembers variables from the scope where it was CREATED, even after that scope has finished
  2. B A function that closes the browser
  3. C A way to end a loop
  4. 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());

  1. A 10 - the closure keeps x alive after outer() returned
  2. B undefined
  3. C ReferenceError
  4. 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());

  1. A 1 2 3 - the closure keeps private state between calls
  2. B 1 1 1
  3. C 0 1 2
  4. 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
  1. A let creates a NEW binding per iteration, so each callback closes over its own copy
  2. B let delays the timers
  3. C let makes the loop synchronous
  4. 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!
  1. A An IIFE that keeps variables private and returns only the public methods
  2. B Importing files
  3. C A class
  4. 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?

  1. A The variable is only reachable through the functions that closed over it - nothing outside can touch it
  2. B By using the private keyword
  3. C By using const
  4. 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
}
  1. A The handler runs long after setup, but still needs the variables from when it was created
  2. B Events require the closure keyword
  3. C Closures make events faster
  4. 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?

  1. A Yes - captured variables cannot be garbage collected while the closure lives (a possible leak if careless)
  2. B No, memory is freed immediately
  3. C Closures use no memory
  4. 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?

  1. A Yes - it holds a live reference, so it can read and write
  2. B No, captured variables are read-only
  3. C Only with const
  4. 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?

  1. A Every function forms a closure over its enclosing scope - we only 'notice' when it outlives that scope
  2. B Only functions with the closure keyword
  3. C Only arrow functions
  4. 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!"
  1. A The greeting parameter
  2. B The name parameter
  3. C Nothing
  4. 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?

  1. A setTimeout callbacks, event handlers, array method callbacks and React hooks
  2. B Only recursion
  3. C Only classes
  4. 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?

  1. A A listener that captures a large element and is never removed keeps it in memory after removal from the page
  2. B Closures always leak
  3. C By using const
  4. 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?

  1. A They let functions carry private, persistent state - enabling callbacks, factories, modules and encapsulation
  2. B They make code shorter
  3. C They speed up loops
  4. 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());

  1. A 1 2 1 - each call to makeCounter creates a SEPARATE closure
  2. B 1 2 3
  3. C 1 1 1
  4. 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); }

  1. A 3 3 3 - all closures share ONE var binding, which is 3 when the timers run
  2. B 0 1 2
  3. C 0 0 0
  4. 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
  1. A Wrap the body in an IIFE that receives i as a parameter
  2. B Add a return statement
  3. C Use a while loop
  4. 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));

  1. A 15 - the closure remembers factor = 3
  2. B 5
  3. C 3
  4. 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
  1. A Turning a multi-argument function into a chain of single-argument functions returning functions
  2. B Sorting an array
  3. C Caching results
  4. 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();

  1. A 99 - closures capture the VARIABLE, not a snapshot of its value
  2. B 1
  3. C undefined
  4. 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));
  };
}
  1. A Caching results in a closure-held object so repeated calls with the same input return instantly
  2. B Writing memos in comments
  3. C Freeing memory
  4. 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));
  1. A To remember the pending timer id between calls, so rapid calls cancel the previous one
  2. B To speed up the function
  3. C To count calls
  4. 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());

  1. A [0, 1, 2] - let gives each closure its own i
  2. B [3, 3, 3]
  3. C [0, 0, 0]
  4. 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()));

  1. A [3, 3, 3] - all three share the single var binding
  2. B [0, 1, 2]
  3. C [0, 0, 0]
  4. 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; }; }

  1. A Ensures fn runs only the FIRST time, caching and returning that result afterwards
  2. B Runs fn once per second
  3. C Deletes fn after use
  4. 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); }, []);

  1. A The effect captured count from the FIRST render, so it always adds 1 to that stale value
  2. B setInterval is not allowed
  3. C The syntax is wrong
  4. 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());

  1. A "hidden" - the block ended, but the closure keeps secret alive
  2. B ReferenceError
  3. C undefined
  4. 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);

  1. A 2 undefined - both methods share the closure, but n is not a property
  2. B 2 2
  3. C 0 0
  4. 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));

  1. A 5 12 - each call to add(2) creates a fresh closure
  2. B 5 5
  3. C 12 12
  4. 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());

  1. A 2 - var ignores the block, so both declarations are the SAME variable
  2. B 1
  3. C undefined
  4. 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

Practice β€œClosures” as a quiz