Scope - Exercises
Multiple Choice Questions
Section titled “Multiple Choice Questions”Question 1
Section titled “Question 1”What is the difference between var, let, and const in terms of scope?
A) No difference in scope
B) var is function-scoped, let and const are block-scoped
C) All are block-scoped
D) All are function-scoped
Answer: B
Explanation: var is function-scoped (or globally scoped), while let and const are block-scoped.
Question 2
Section titled “Question 2”What will this code output?
function test() { if (true) { var a = 1; let b = 2; } console.log(a); // ? console.log(b); // ?}A) 1, 2 B) 1, undefined C) 1, ReferenceError D) undefined, ReferenceError
Answer: C
Explanation: var a is function-scoped so it’s accessible, but let b is block-scoped and throws a ReferenceError when accessed outside its block.
Question 3
Section titled “Question 3”What is a closure in JavaScript? A) A function that’s closed to modifications B) A function that has access to variables in its outer scope C) A function that can’t be called D) A private function
Answer: B Explanation: A closure is a function that has access to variables from its outer (enclosing) scope even after the outer function has finished executing.
Question 4
Section titled “Question 4”What will this code output?
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100);}A) 0, 1, 2 B) 3, 3, 3 C) 0, 0, 0 D) Error
Answer: B
Explanation: Due to var being function-scoped and the asynchronous nature of setTimeout, all callbacks reference the same i which has value 3 after the loop completes.
Question 5
Section titled “Question 5”How can you fix the previous code to print 0, 1, 2?
A) Use let instead of var
B) Use an IIFE (Immediately Invoked Function Expression)
C) Use const instead of var
D) Both A and B
Answer: D
Explanation: Both using let (block scope) and IIFE (creating new scope) would capture the correct value of i.
Question 6
Section titled “Question 6”What is the global scope in a browser environment?
A) document
B) window
C) global
D) this
Answer: B
Explanation: In browser environments, the global scope is the window object.
Practical Coding Exercises
Section titled “Practical Coding Exercises”Exercise 1: Variable Scope Demonstration
Section titled “Exercise 1: Variable Scope Demonstration”Practice understanding the difference between function scope and block scope:
function demonstrateScope() { // TODO: Demonstrate var (function scope) if (true) { var functionScoped = "I'm function scoped"; } // TODO: Try to console.log functionScoped here - it should work
// TODO: Demonstrate let (block scope) if (true) { let blockScoped = "I'm block scoped"; // TODO: console.log blockScoped here - it should work } // TODO: Try to console.log blockScoped here - it should cause an error // (Comment out the line to avoid the error)
// TODO: Add a for loop example with var // TODO: Show that the var loop variable is accessible after the loop
// TODO: Add a for loop example with let // TODO: Show that the let loop variable is NOT accessible after the loop}
// TODO: Call your function to test it// demonstrateScope();Expected Output:
varvariables should be accessible outside their blockletvariables should only be accessible within their block- Loop variables behave differently with
varvslet
Solution:
function demonstrateScope() { console.log("=== Function vs Block Scope ===");
// Var example - function scoped if (true) { var functionScoped = "I'm function scoped"; } console.log("var outside block:", functionScoped); // Works
// Let example - block scoped if (true) { let blockScoped = "I'm block scoped"; console.log("let inside block:", blockScoped); // Works } // console.log(blockScoped); // ReferenceError
// Loop example for (var i = 0; i < 3; i++) { // var i is function scoped } console.log("var after loop:", i); // 3
for (let j = 0; j < 3; j++) { // let j is block scoped } // console.log(j); // ReferenceError
// Const example if (true) { const blockConstant = "I'm a block-scoped constant"; console.log("const inside block:", blockConstant); } // console.log(blockConstant); // ReferenceError}Exercise 2: Closure Practice
Section titled “Exercise 2: Closure Practice”Create a function that demonstrates closure by returning an inner function:
// TODO: Create a function called createCounter that uses closurefunction createCounter() { // TODO: Create a private counter variable starting at 0 // TODO: Return a function that increments and returns the counter}
// TODO: Create a function that asks the user how many times to countfunction interactiveCounterTest() { // TODO: Prompt the user: "How many times would you like to count?" const times = parseInt(prompt("How many times would you like to count?"));
// TODO: Create a counter using your createCounter function const counter = createCounter();
// TODO: Use a loop to call the counter the specified number of times // TODO: Console.log each count with a message like "Count: 1", "Count: 2", etc.
// TODO: Create a second counter to show they are independent const counter2 = createCounter(); console.log("Second counter starts fresh:", counter2()); // Should be 1}
// TODO: Call your interactive function to test it// interactiveCounterTest();Expected Behavior:
- User inputs a number (e.g., 5)
- Counter counts from 1 to that number
- Second counter shows it starts independently at 1
Solution:
function createCounter() { let count = 0;
return function () { count++; return count; };}Exercise 3: Interactive Bank Account
Section titled “Exercise 3: Interactive Bank Account”Use closures to create a simple banking module with user interaction:
// TODO: Create a function called createBankAccount that takes initialBalancefunction createBankAccount(initialBalance) { // TODO: Create a private balance variable // TODO: Return an object with methods: deposit, withdraw, getBalance}
// TODO: Create an interactive banking simulationfunction bankingSimulation() { // TODO: Prompt user for initial balance const initialAmount = parseFloat(prompt("Enter your initial balance: $"));
// TODO: Create a bank account with the initial balance const account = createBankAccount(initialAmount);
// TODO: Create a loop that continues until user chooses to quit let continueTransaction = true;
while (continueTransaction) { // TODO: Show current balance console.log(`Current Balance: $${account.getBalance()}`);
// TODO: Prompt user for action: "1-Deposit, 2-Withdraw, 3-Check Balance, 4-Quit" const action = prompt( "Choose an action:\n1 - Deposit\n2 - Withdraw\n3 - Check Balance\n4 - Quit", );
// TODO: Use if/else or switch to handle each action if (action === "1") { // TODO: Prompt for deposit amount and call deposit method const depositAmount = parseFloat(prompt("Enter deposit amount: $")); console.log(account.deposit(depositAmount)); } else if (action === "2") { // TODO: Prompt for withdrawal amount and call withdraw method const withdrawAmount = parseFloat(prompt("Enter withdrawal amount: $")); console.log(account.withdraw(withdrawAmount)); } else if (action === "3") { // TODO: Display current balance console.log(`Your balance is: $${account.getBalance()}`); } else if (action === "4") { // TODO: Set continueTransaction to false and show goodbye message continueTransaction = false; console.log("Thank you for banking with us!"); } else { console.log("Invalid choice. Please try again."); } }}
// TODO: Call your banking simulation to test it// bankingSimulation();Expected Features:
- User sets initial balance
- Interactive menu for deposit/withdraw/check balance
- Private balance that can’t be accessed directly
- Input validation and error messages
Solution:
function createBankAccount(initialBalance) { let balance = initialBalance;
return { deposit: function (amount) { if (amount > 0) { balance += amount; return `Deposited $${amount}. New balance: $${balance}`; } return "Invalid deposit amount"; },
withdraw: function (amount) { if (amount > 0 && amount <= balance) { balance -= amount; return `Withdrew $${amount}. New balance: $${balance}`; } return "Invalid withdrawal amount or insufficient funds"; },
getBalance: function () { return balance; }, };}Exercise 4: Fix the Loop Closure Problem
Section titled “Exercise 4: Fix the Loop Closure Problem”Fix this code so it prints 0, 1, 2 instead of 3, 3, 3.
// Problematic codefunction problematicLoop() { for (var i = 0; i < 3; i++) { setTimeout(function () { console.log("Problem:", i); }, 100); }}
// Fix using letfunction fixedWithLet() { // Your solution here}
// Fix using IIFEfunction fixedWithIIFE() { // Your solution here}
// Fix using bindfunction fixedWithBind() { // Your solution here}Solutions:
// Fix using letfunction fixedWithLet() { for (let i = 0; i < 3; i++) { setTimeout(function () { console.log("Let solution:", i); }, 100); }}
// Fix using IIFEfunction fixedWithIIFE() { for (var i = 0; i < 3; i++) { (function (index) { setTimeout(function () { console.log("IIFE solution:", index); }, 100); })(i); }}
// Fix using bindfunction fixedWithBind() { for (var i = 0; i < 3; i++) { setTimeout( function (index) { console.log("Bind solution:", index); }.bind(null, i), 100, ); }}Exercise 5: Scope Chain Understanding
Section titled “Exercise 5: Scope Chain Understanding”Create nested functions to demonstrate the scope chain.
function outerFunction(x) { // Create variables and inner functions to demonstrate scope chain
function middleFunction(y) { // This should access x from outer scope
function innerFunction(z) { // This should access x, y, and z // Return a message using all three variables }
return innerFunction; }
return middleFunction;}
// Test the scope chainconst test = outerFunction(1)(2)(3);console.log(test); // Should use all three valuesSolution:
function outerFunction(x) { console.log("Outer function, x =", x);
function middleFunction(y) { console.log("Middle function, x =", x, "y =", y);
function innerFunction(z) { console.log("Inner function, x =", x, "y =", y, "z =", z); return `Sum of x(${x}) + y(${y}) + z(${z}) = ${x + y + z}`; }
return innerFunction; }
return middleFunction;}JavaScript Test Functions
Section titled “JavaScript Test Functions”// Test function for scope exercisesfunction testScopeExercises() { console.log("=== Testing Scope Exercises ===");
// Test createCounter const counter = createCounter(); const test1 = counter() === 1 && counter() === 2; console.log("createCounter test:", test1 ? "PASS" : "FAIL");
// Test createBankAccount const account = createBankAccount(100); account.deposit(50); const test2 = account.getBalance() === 150; console.log("createBankAccount test:", test2 ? "PASS" : "FAIL");
// Test scope chain const scopeTest = outerFunction(1)(2)(3); const test3 = scopeTest.includes("6"); // Sum should be 6 console.log("scope chain test:", test3 ? "PASS" : "FAIL");
// Test independent counters const counter1 = createCounter(); const counter2 = createCounter(); counter1(); counter1(); const test4 = counter1() === 3 && counter2() === 1; console.log("independent closures test:", test4 ? "PASS" : "FAIL");}
// Run teststestScopeExercises();Challenge Problems
Section titled “Challenge Problems”Challenge 1: Function Factory with Configuration
Section titled “Challenge 1: Function Factory with Configuration”Create a function factory that produces configured functions.
function createValidator(config) { // config = { type: 'email'|'phone'|'password', options: {...} } // Return a validator function based on the config
const validators = { email: (value, options) => { // Basic email validation return value.includes("@") && value.includes("."); },
phone: (value, options) => { // Phone number validation const cleaned = value.replace(/\D/g, ""); return cleaned.length === 10; },
password: (value, options) => { // Password validation with configurable rules const minLength = options.minLength || 8; const requireSpecial = options.requireSpecial || false;
if (value.length < minLength) return false; if (requireSpecial && !/[!@#$%^&*]/.test(value)) return false;
return true; }, };
return function (value) { // Your implementation here };}
// Test your factoryconst emailValidator = createValidator({ type: "email" });const passwordValidator = createValidator({ type: "password", options: { minLength: 10, requireSpecial: true },});
console.log(emailValidator("test@email.com")); // trueconsole.log(passwordValidator("short")); // falseconsole.log(passwordValidator("longenough!@")); // trueSolution:
function createValidator(config) { const validators = { email: (value, options) => { return value.includes("@") && value.includes("."); },
phone: (value, options) => { const cleaned = value.replace(/\D/g, ""); return cleaned.length === 10; },
password: (value, options) => { const minLength = options.minLength || 8; const requireSpecial = options.requireSpecial || false;
if (value.length < minLength) return false; if (requireSpecial && !/[!@#$%^&*]/.test(value)) return false;
return true; }, };
const validator = validators[config.type]; const options = config.options || {};
return function (value) { if (!validator) { throw new Error(`Unknown validator type: ${config.type}`); } return validator(value, options); };}Challenge 2: Memoization with Closures
Section titled “Challenge 2: Memoization with Closures”Create a memoization function that caches expensive function calls.
function memoize(fn) { // Create a cache using closure // Return a function that checks cache before calling fn}
// Test function (expensive operation)function fibonacci(n) { console.log(`Calculating fibonacci(${n})`); if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2);}
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // Should cache intermediate resultsconsole.log(memoizedFib(10)); // Should use cached resultSolution:
function memoize(fn) { const cache = {};
return function (...args) { const key = JSON.stringify(args);
if (key in cache) { console.log("Cache hit for", key); return cache[key]; }
console.log("Cache miss for", key); const result = fn.apply(this, args); cache[key] = result; return result; };}Challenge 3: Private Variable System
Section titled “Challenge 3: Private Variable System”Create a system for managing private variables in objects.
function createPrivateVars() { // Create a system where objects can have truly private variables // that can only be accessed through specific methods
const privateStore = new WeakMap();
return { create: function (publicMethods) { // Create object with private variables },
get: function (obj, key) { // Get private variable },
set: function (obj, key, value) { // Set private variable }, };}
// Usage example:const privateVars = createPrivateVars();
const obj = privateVars.create({ getName: function () { return privateVars.get(this, "name"); }, setName: function (name) { privateVars.set(this, "name", name); },});
obj.setName("John");console.log(obj.getName()); // 'John'console.log(obj.name); // undefined (truly private)Solution:
function createPrivateVars() { const privateStore = new WeakMap();
return { create: function (publicMethods) { const obj = {}; privateStore.set(obj, {});
// Copy public methods to the object Object.assign(obj, publicMethods);
return obj; },
get: function (obj, key) { const privateData = privateStore.get(obj); return privateData ? privateData[key] : undefined; },
set: function (obj, key, value) { const privateData = privateStore.get(obj); if (privateData) { privateData[key] = value; } }, };}Challenge 4: Debounce and Throttle
Section titled “Challenge 4: Debounce and Throttle”Implement debounce and throttle functions using closures.
function debounce(func, delay) { // Implement debounce: delay execution until after delay ms have passed // since the last time it was invoked}
function throttle(func, limit) { // Implement throttle: limit execution to once per limit ms}
// Test functionsconst debouncedLog = debounce((msg) => console.log("Debounced:", msg), 300);const throttledLog = throttle((msg) => console.log("Throttled:", msg), 300);
// Simulate rapid callsfor (let i = 0; i < 5; i++) { setTimeout(() => debouncedLog(`Message ${i}`), i * 50);}Solution:
function debounce(func, delay) { let timeoutId;
return function (...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); };}
function throttle(func, limit) { let inThrottle;
return function (...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => (inThrottle = false), limit); } };}Debugging Exercises
Section titled “Debugging Exercises”Debug Exercise 1
Section titled “Debug Exercise 1”Find and fix the scope issue:
var buttons = document.querySelectorAll("button");
for (var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", function () { console.log("Button", i, "clicked"); // Always logs the same i });}Issue: var i is function-scoped, so all event listeners reference the same variable.
Fix:
const buttons = document.querySelectorAll("button");
for (let i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", function () { console.log("Button", i, "clicked"); });}
// Alternative fix with IIFE:for (var i = 0; i < buttons.length; i++) { (function (index) { buttons[index].addEventListener("click", function () { console.log("Button", index, "clicked"); }); })(i);}Debug Exercise 2
Section titled “Debug Exercise 2”Fix the closure memory leak:
function createHandler() { const largeData = new Array(1000000).fill("data");
return { process: function () { // Only uses a small part of largeData console.log(largeData[0]); }, };}
// This keeps largeData in memory even though we only need the first elementFix:
function createHandler() { const largeData = new Array(1000000).fill("data"); const firstElement = largeData[0]; // Extract only what we need
return { process: function () { console.log(firstElement); }, };}Debug Exercise 3
Section titled “Debug Exercise 3”Fix the this binding issue in closures:
const obj = { name: "MyObject", method: function () { setTimeout(function () { console.log(this.name); // undefined - wrong this context }, 100); },};Fix:
const obj = { name: "MyObject", method: function () { // Solution 1: Store this reference const self = this; setTimeout(function () { console.log(self.name); }, 100);
// Solution 2: Use arrow function setTimeout(() => { console.log(this.name); }, 100);
// Solution 3: Use bind setTimeout( function () { console.log(this.name); }.bind(this), 100, ); },};Real-World Applications
Section titled “Real-World Applications”Application 1: Configuration Manager
Section titled “Application 1: Configuration Manager”Create a configuration manager with private settings.
function createConfigManager(defaultConfig) { let config = { ...defaultConfig }; const listeners = [];
return { get: function (key) { return config[key]; },
set: function (key, value) { const oldValue = config[key]; config[key] = value;
// Notify listeners listeners.forEach((listener) => { listener(key, value, oldValue); }); },
subscribe: function (listener) { listeners.push(listener);
// Return unsubscribe function return function () { const index = listeners.indexOf(listener); if (index > -1) { listeners.splice(index, 1); } }; },
reset: function () { config = { ...defaultConfig }; }, };}
// Usageconst config = createConfigManager({ theme: "light", lang: "en" });const unsubscribe = config.subscribe((key, newVal, oldVal) => { console.log(`Config changed: ${key} from ${oldVal} to ${newVal}`);});
config.set("theme", "dark"); // Triggers listenerApplication 2: Event Emitter with Scope Control
Section titled “Application 2: Event Emitter with Scope Control”Create an event emitter that uses closures for namespace isolation.
function createEventEmitter() { const events = {};
return { on: function (event, listener) { if (!events[event]) { events[event] = []; } events[event].push(listener); },
off: function (event, listenerToRemove) { if (!events[event]) return;
events[event] = events[event].filter( (listener) => listener !== listenerToRemove, ); },
emit: function (event, ...args) { if (!events[event]) return;
events[event].forEach((listener) => { listener(...args); }); },
once: function (event, listener) { const onceWrapper = (...args) => { listener(...args); this.off(event, onceWrapper); }; this.on(event, onceWrapper); }, };}Application 3: Rate Limiter
Section titled “Application 3: Rate Limiter”Implement a rate limiter using closures.
function createRateLimiter(maxRequests, timeWindow) { const requests = [];
return function (requestHandler) { const now = Date.now();
// Remove old requests outside the time window while (requests.length > 0 && requests[0] <= now - timeWindow) { requests.shift(); }
// Check if we've exceeded the limit if (requests.length >= maxRequests) { throw new Error("Rate limit exceeded"); }
// Add current request timestamp requests.push(now);
// Execute the request return requestHandler(); };}
// Usageconst limitedFetch = createRateLimiter(5, 1000); // 5 requests per second
try { limitedFetch(() => console.log("API call made"));} catch (error) { console.log(error.message);}Self-Assessment Checklist
Section titled “Self-Assessment Checklist”- I understand the difference between function scope and block scope
- I know when to use
var,let, andconst - I understand what closures are and how they work
- I can identify and fix scope-related bugs
- I understand the scope chain and variable lookup
- I can use closures to create private variables
- I can implement the module pattern using closures
- I understand how
thisbehaves in different scopes - I can debug common scope and closure issues
- I can apply scope concepts to solve real-world problems
Additional Practice Resources
Section titled “Additional Practice Resources”- Practice with nested function examples
- Implement various design patterns using closures
- Debug scope-related issues in existing code
- Create utility functions that leverage closures
- Build modules with private and public interfaces
- Practice with the temporal dead zone and hoisting
- Experiment with different ways to bind
this
Next Topic: 08 - Arrays and Objects