Chapter 24 Advanced 50 Questions

Practice Questions — Closures, Scope, and Hoisting

← Back to Notes
8 Easy
9 Medium
10 Hard

Topic-Specific Questions

Question 1
Easy
What is the output?
let x = 10;
function foo() {
  console.log(x);
}
foo();
Inner functions can access outer (including global) variables.
10
Question 2
Easy
What is the output?
function test() {
  let a = 5;
  return a;
}
console.log(test());
// console.log(a);
Variables declared inside a function are not accessible outside.
5 (and the commented line would throw ReferenceError)
Question 3
Easy
What is the output?
console.log(x);
var x = 5;
console.log(x);
var declarations are hoisted. The value is NOT hoisted.
undefined
5
Question 4
Easy
What is the output?
if (true) {
  var a = 10;
  let b = 20;
}
console.log(a);
// console.log(b);
var ignores block scope. let respects block scope.
10 (and the commented line would throw ReferenceError)
Question 5
Medium
What is the output?
function make() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}
const fn = make();
console.log(fn());
console.log(fn());
console.log(fn());
The returned function forms a closure over 'count'.
1
2
3
Question 6
Medium
What is the output?
function greet(greeting) {
  return function(name) {
    return greeting + ", " + name;
  };
}
const hi = greet("Hi");
const hello = greet("Hello");
console.log(hi("Aarav"));
console.log(hello("Priya"));
Each call to greet creates a separate closure with its own 'greeting'.
Hi, Aarav
Hello, Priya
Question 7
Medium
What is the output?
sayHello();
function sayHello() {
  console.log("Hello!");
}

try { sayBye(); } catch(e) { console.log(e.message); }
var sayBye = function() {
  console.log("Bye!");
};
Function declarations are fully hoisted. Function expressions assigned to var are NOT.
Hello!
sayBye is not a function
Question 8
Hard
What is the output?
var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function() { return i; });
}
console.log(funcs[0]());
console.log(funcs[1]());
console.log(funcs[2]());
This is the classic closure trap with var.
3
3
3
Question 9
Hard
What is the output?
var funcs = [];
for (let i = 0; i < 3; i++) {
  funcs.push(function() { return i; });
}
console.log(funcs[0]());
console.log(funcs[1]());
console.log(funcs[2]());
let creates a new variable for each iteration.
0
1
2
Question 10
Hard
What is the output?
function outer() {
  let x = 10;
  function inner() {
    console.log(x);
  }
  x = 20;
  return inner;
}
const fn = outer();
fn();
Closures capture a reference to the variable, not a snapshot of its value.
20
Question 11
Hard
What is the output?
console.log(typeof a);
console.log(typeof b);
var a = 1;
let b = 2;
var is hoisted as undefined. But what happens with let?
The first line prints undefined. The second line throws ReferenceError: Cannot access 'b' before initialization.
Question 12
Medium
What is a closure in simple terms? Give a one-sentence definition.
Think about what the inner function remembers.
A closure is a function that retains access to variables from its outer (enclosing) function's scope, even after the outer function has finished executing.
Question 13
Hard
What is the Temporal Dead Zone (TDZ) and which variables are affected by it?
Think about what happens between hoisting and the declaration line.
The Temporal Dead Zone is the period between a let or const variable being hoisted and its actual declaration in the code. During the TDZ, the variable exists (it has been hoisted) but accessing it throws a ReferenceError because it has not been initialized. var does NOT have a TDZ -- it is initialized to undefined during hoisting.
Question 14
Easy
What is the output?
function outer() {
  let x = 5;
  function inner() {
    return x * 2;
  }
  return inner();
}
console.log(outer());
inner can access x from outer's scope.
10
Question 15
Easy
What is the output?
const x = 10;
if (true) {
  const x = 20;
  console.log(x);
}
console.log(x);
const is block-scoped. The inner x shadows the outer x inside the block.
20
10
Question 16
Medium
What is the output?
function makeAdder(x) {
  return function(y) { return x + y; };
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(3) + add10(3));
add5 remembers x=5, add10 remembers x=10.
21
Question 17
Medium
What is the output?
for (var i = 0; i < 3; i++) {
  // just looping
}
console.log(i);
var is not block-scoped. It leaks out of the for loop.
3
Question 18
Hard
What is the output?
function makeCounter() {
  let count = 0;
  return {
    inc: function() { return ++count; },
    get: function() { return count; }
  };
}
const a = makeCounter();
const b = makeCounter();
a.inc(); a.inc(); a.inc();
b.inc();
console.log(a.get());
console.log(b.get());
Each makeCounter call creates a separate closure with its own count.
3
1

Mixed & Application Questions

Question 1
Easy
What is the output?
var a = 1;
function foo() {
  var a = 2;
  console.log(a);
}
foo();
console.log(a);
Each function has its own scope. The inner 'a' shadows the outer 'a'.
2
1
Question 2
Easy
What is the output?
for (let i = 0; i < 3; i++) {
  // just looping
}
try {
  console.log(i);
} catch(e) {
  console.log("Error: i is not defined");
}
let is block-scoped. The loop body is a block.
Error: i is not defined
Question 3
Medium
What is the output?
(function() {
  var x = 10;
  console.log(x);
})();
try {
  console.log(x);
} catch(e) {
  console.log("x not defined");
}
IIFE creates a function scope. Variables inside are not accessible outside.
10
x not defined
Question 4
Medium
What is the output?
function createAdder(x) {
  return function(y) {
    return x + y;
  };
}
const add5 = createAdder(5);
const add10 = createAdder(10);
console.log(add5(3));
console.log(add10(3));
console.log(add5(add10(1)));
Each closure remembers its own x. The last line nests the calls.
8
13
16
Question 5
Medium
What is the output?
let a = 1;
function foo() {
  console.log(a);
  let a = 2;
  console.log(a);
}
let is hoisted within the function but enters TDZ.
ReferenceError: Cannot access 'a' before initialization
Question 6
Hard
What is the output?
function createFunctions() {
  var result = [];
  for (var i = 0; i < 3; i++) {
    result.push((function(j) {
      return function() { return j; };
    })(i));
  }
  return result;
}
var fns = createFunctions();
console.log(fns[0]());
console.log(fns[1]());
console.log(fns[2]());
The IIFE captures the current value of i as j in each iteration.
0
1
2
Question 7
Hard
What is the output?
const counter = (function() {
  let count = 0;
  return {
    up: function() { return ++count; },
    down: function() { return --count; },
    value: function() { return count; }
  };
})();

console.log(counter.up());
console.log(counter.up());
console.log(counter.down());
console.log(counter.value());
console.log(counter.count);
This is the module pattern. count is private.
1
2
1
1
undefined
Question 8
Hard
What is the output?
for (var i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log("var: " + i);
  }, 0);
}

for (let j = 1; j <= 3; j++) {
  setTimeout(function() {
    console.log("let: " + j);
  }, 0);
}
setTimeout runs after the loop finishes. var shares one i, let creates a j per iteration.
var: 4
var: 4
var: 4
let: 1
let: 2
let: 3
Question 9
Hard
Explain why the module pattern works for creating private variables in JavaScript.
Think about what the IIFE does and what the returned object can access.
The module pattern uses an IIFE (Immediately Invoked Function Expression) that declares variables inside its function scope. These variables cannot be accessed from outside the function. The IIFE returns an object with methods that form closures over the private variables. Only these methods can read or modify the private data. Code outside the module can use the methods but cannot directly access the private variables.

Multiple Choice Questions

MCQ 1
What is scope in JavaScript?
  • A. The speed of code execution
  • B. The region of code where a variable is accessible
  • C. The size of a variable
  • D. The type of a variable
Answer: B
B is correct. Scope determines where a variable can be accessed. Variables in function scope are only accessible inside that function.
MCQ 2
What is a closure?
  • A. A function that closes the browser tab
  • B. A function that has access to its outer function's variables even after the outer function returns
  • C. A function that cannot be called
  • D. A function without parameters
Answer: B
B is correct. A closure is a function plus its lexical environment. It retains access to outer variables even after the outer function has finished executing.
MCQ 3
What does hoisting do to var declarations?
  • A. Moves the entire line to the top
  • B. Moves the declaration to the top and initializes it to undefined
  • C. Moves the value to the top
  • D. Deletes the variable
Answer: B
B is correct. Hoisting moves the var declaration to the top of its scope and initializes it to undefined. The value assignment stays in its original position.
MCQ 4
Which keyword creates a block-scoped variable?
  • A. var
  • B. function
  • C. let
  • D. global
Answer: C
C is correct. let (and const) create block-scoped variables that only exist inside the { } block where they are declared.
MCQ 5
What happens when you access a let variable before its declaration?
  • A. It returns undefined
  • B. It returns null
  • C. It throws a ReferenceError (Temporal Dead Zone)
  • D. It returns the global value
Answer: C
C is correct. let and const have a Temporal Dead Zone. Accessing them before declaration throws ReferenceError. This is different from var, which returns undefined.
MCQ 6
In the classic loop trap, why do all var closures return the same value?
  • A. Because JavaScript copies the variable each time
  • B. Because all closures share the same var variable
  • C. Because setTimeout always returns the last value
  • D. Because var creates a new variable each iteration
Answer: B
B is correct. var is function-scoped, not block-scoped. There is only ONE i variable shared by all iterations. By the time callbacks run, the loop is done and i has its final value.
MCQ 7
What is an IIFE?
  • A. An internal function expression
  • B. An immediately invoked function expression that runs right away
  • C. An infinite function executor
  • D. An interface for function exports
Answer: B
B is correct. An IIFE is a function that is defined and immediately executed: (function() { ... })(). It creates a private scope.
MCQ 8
Which of these is fully hoisted (both name and body)?
  • A. var f = function() {}
  • B. const f = () => {}
  • C. function f() {}
  • D. let f = function() {}
Answer: C
C is correct. Only function declarations (function name() { }) are fully hoisted. Function expressions and arrow functions follow their variable keyword rules.
MCQ 9
Do closures capture variables by value or by reference?
  • A. By value -- they take a snapshot
  • B. By reference -- they see changes made after closure creation
  • C. By value for primitives, by reference for objects
  • D. It depends on the browser
Answer: B
B is correct. Closures capture a reference to the variable, not its value. If the variable changes after the closure is created, the closure sees the updated value.
MCQ 10
What is the module pattern used for?
  • A. Loading external modules
  • B. Creating objects with private variables using closures
  • C. Splitting code into files
  • D. Importing npm packages
Answer: B
B is correct. The module pattern uses an IIFE to create private variables and returns an object with public methods. The methods form closures over the private variables, providing encapsulation.
MCQ 11
Why does let fix the loop closure trap?
  • A. let is faster than var
  • B. let creates a new variable for each iteration of the loop
  • C. let prevents closures from forming
  • D. let makes setTimeout synchronous
Answer: B
B is correct. let is block-scoped, and each iteration of a for loop is a new block. So each closure captures its own separate variable with the correct value from that iteration.
MCQ 12
What is lexical scope?
  • A. Scope determined by where a function is called
  • B. Scope determined by where a function is written in the source code
  • C. Scope determined at runtime by the engine
  • D. Scope that only applies to objects
Answer: B
B is correct. Lexical (or static) scope means the scope is determined by the position of the code when it is written, not when it is executed. Inner functions can access variables from their enclosing scope in the source code.
MCQ 13
What value does a var variable have before it is assigned?
  • A. null
  • B. 0
  • C. undefined
  • D. It throws an error
Answer: C
C is correct. var declarations are hoisted and initialized to undefined. Accessing a var before its assignment line returns undefined.
MCQ 14
What is the main practical use of closures?
  • A. Making code run faster
  • B. Creating private variables and maintaining state between function calls
  • C. Preventing memory leaks
  • D. Converting functions to strings
Answer: B
B is correct. Closures are used to create private variables (that cannot be accessed from outside) and to maintain state between calls (like a counter that remembers its count).
MCQ 15
What is the output of: (function(x) { return x * 2; })(5)?
  • A. undefined
  • B. function
  • C. 10
  • D. SyntaxError
Answer: C
C is correct. This is an IIFE that takes 5 as an argument and returns 5 * 2 = 10. The function is defined and immediately called.
MCQ 16
In the module pattern, how are private variables created?
  • A. Using the private keyword
  • B. Using variables declared inside an IIFE that are not returned
  • C. Using underscore prefix on variable names
  • D. Using Object.freeze
Answer: B
B is correct. Variables declared inside an IIFE are trapped in its function scope. Only the methods returned by the IIFE can access them. Code outside cannot reach them.
MCQ 17
What is the difference between function scope and block scope?
  • A. They are the same thing
  • B. Function scope is for var, block scope is for let/const -- blocks include if, for, while curly braces
  • C. Block scope only applies to classes
  • D. Function scope applies to arrow functions only
Answer: B
B is correct. var is function-scoped (only trapped by function boundaries). let/const are block-scoped (trapped by any { } including if, for, while, etc.).

Coding Challenges

Challenge 1: Counter with Reset

Easy
Create a function makeCounter that returns an object with increment, decrement, getCount, and reset methods. The count variable should be private (not accessible directly from outside).
Sample Input
counter.increment() x3, counter.decrement() x1, counter.getCount()
Sample Output
1, 2, 3, 2, getCount: 2, reset: 0
The count variable must be private. Only the returned methods should be able to access it.
function makeCounter(start) {
  let count = start || 0;
  return {
    increment: function() { return ++count; },
    decrement: function() { return --count; },
    getCount: function() { return count; },
    reset: function() { count = start || 0; return count; }
  };
}

const c = makeCounter(0);
console.log(c.increment()); // 1
console.log(c.increment()); // 2
console.log(c.increment()); // 3
console.log(c.decrement()); // 2
console.log(c.getCount());  // 2
console.log(c.reset());     // 0
console.log(c.count);       // undefined (private!)

Challenge 2: Once Function

Easy
Write a function called once that takes a function as an argument and returns a new function that can only be called once. Subsequent calls should return the result of the first call without executing the original function again.
Sample Input
const add = once((a, b) => a + b); add(2, 3); add(5, 5);
Sample Output
5, 5 (second call returns cached result)
Use a closure to track whether the function has been called.
function once(fn) {
  let called = false;
  let result;
  return function() {
    if (!called) {
      result = fn.apply(this, arguments);
      called = true;
    }
    return result;
  };
}

const addOnce = once(function(a, b) {
  console.log("Computing...");
  return a + b;
});
console.log(addOnce(2, 3)); // Computing... 5
console.log(addOnce(5, 5)); // 5 (no Computing...)
console.log(addOnce(10, 10)); // 5 (still cached)

Challenge 3: Rate Limiter

Medium
Write a function called createRateLimiter that takes a function and a time limit in milliseconds. It should return a new function that can only be called once within the time limit. Additional calls within the limit are ignored.
Sample Input
const limited = createRateLimiter(fn, 1000); limited(); limited(); (after 1s) limited();
Sample Output
First call: executed. Second call: ignored. After 1s: executed.
Use closure to track the last call time. Use Date.now() for timing.
function createRateLimiter(fn, limit) {
  let lastCalled = 0;
  return function() {
    const now = Date.now();
    if (now - lastCalled >= limit) {
      lastCalled = now;
      return fn.apply(this, arguments);
    }
    console.log("Rate limited, try again later");
    return undefined;
  };
}

const limitedLog = createRateLimiter(function(msg) {
  console.log("Logged: " + msg);
}, 1000);

limitedLog("hello");  // Logged: hello
limitedLog("world");  // Rate limited
// After 1 second:
// limitedLog("again"); // Logged: again

Challenge 4: Private Collection

Medium
Create a function called createCollection that returns an object with add, remove, has, getAll, and size methods. The internal array should be completely private. getAll should return a copy, not the original.
Sample Input
add('Aarav'), add('Priya'), has('Aarav'), remove('Aarav'), size()
Sample Output
add: true, add: true, has: true, remove: true, size: 1
Use closures for privacy. getAll must return a copy. Prevent duplicates.
function createCollection() {
  const items = [];
  return {
    add: function(item) {
      if (!items.includes(item)) {
        items.push(item);
        return true;
      }
      return false;
    },
    remove: function(item) {
      const idx = items.indexOf(item);
      if (idx !== -1) {
        items.splice(idx, 1);
        return true;
      }
      return false;
    },
    has: function(item) { return items.includes(item); },
    getAll: function() { return [...items]; },
    size: function() { return items.length; }
  };
}

const col = createCollection();
console.log(col.add("Aarav"));   // true
console.log(col.add("Priya"));   // true
console.log(col.add("Aarav"));   // false (duplicate)
console.log(col.has("Aarav"));   // true
console.log(col.remove("Aarav")); // true
console.log(col.size());          // 1

Challenge 5: Fix the Loop Closure Bug

Hard
The following code has a bug. Each button should log its own index when clicked, but all buttons log 5. Fix it using three different approaches: (1) let, (2) IIFE, (3) closure function.
Sample Input
5 buttons created in a loop with var
Sample Output
Click button 0 -> logs 0, click button 1 -> logs 1, etc.
Provide three different solutions.
// BUGGY CODE:
// for (var i = 0; i < 5; i++) {
//   buttons[i].addEventListener('click', function() { console.log(i); });
// }

// FIX 1: Use let
for (let i = 0; i < 5; i++) {
  buttons[i].addEventListener('click', function() { console.log(i); });
}

// FIX 2: IIFE
for (var i = 0; i < 5; i++) {
  (function(j) {
    buttons[j].addEventListener('click', function() { console.log(j); });
  })(i);
}

// FIX 3: Closure function
function createHandler(index) {
  return function() { console.log(index); };
}
for (var i = 0; i < 5; i++) {
  buttons[i].addEventListener('click', createHandler(i));
}

Challenge 6: Function Pipeline Builder

Hard
Write a function called pipe that takes multiple functions as arguments and returns a new function that applies them left to right. pipe(double, addOne)(5) should return 11 (5*2 = 10, 10+1 = 11).
Sample Input
pipe(double, addOne, square)(3)
Sample Output
49 (3*2=6, 6+1=7, 7*7=49)
Use closures and reduce. The piped function should work with any number of functions.
function pipe() {
  const fns = Array.from(arguments);
  return function(input) {
    return fns.reduce(function(result, fn) {
      return fn(result);
    }, input);
  };
}

function double(x) { return x * 2; }
function addOne(x) { return x + 1; }
function square(x) { return x * x; }

const transform = pipe(double, addOne, square);
console.log(transform(3)); // 49 (3*2=6, +1=7, 7^2=49)
console.log(transform(5)); // 121 (5*2=10, +1=11, 11^2=121)

const simple = pipe(double, double);
console.log(simple(4)); // 16 (4*2=8, 8*2=16)

Need to Review the Concepts?

Go back to the detailed notes for this chapter.

Read Chapter Notes

Want to learn web development with a live mentor?

Explore our Frontend Masterclass