javascript
exercises
exercises.js⚡javascript
/**
* =====================================================
* 5.6 SCOPE AND CLOSURES - EXERCISES
* =====================================================
* Practice scope and closure concepts
*/
/**
* Exercise 1: Scope Identification
*
* For each variable, identify its scope type and
* whether it's accessible from the marked locations.
*/
console.log('Exercise 1: Scope Identification');
var globalVar = 'global';
function outerFunction() {
let outerLet = 'outer';
if (true) {
const blockConst = 'block';
var ifVar = 'if var';
// Location A: What variables are accessible here?
}
// Location B: What variables are accessible here?
function innerFunction() {
let innerLet = 'inner';
// Location C: What variables are accessible here?
}
}
// Location D: What variables are accessible here?
// TODO: Write your answers as comments
/**
* Exercise 2: Basic Counter Closure
*
* Create a counter using closure with increment,
* decrement, and getValue methods.
*/
console.log('\nExercise 2:');
function createCounter(initialValue = 0) {
// TODO: Implement using closure
// let count = initialValue;
// return object with increment, decrement, getValue
}
// Test:
// const counter = createCounter(10);
// console.log(counter.getValue()); // 10
// counter.increment();
// console.log(counter.getValue()); // 11
// counter.decrement();
// counter.decrement();
// console.log(counter.getValue()); // 9
/**
* Exercise 3: Private Variables
*
* Create a person object with private age that can only
* be modified through birthday() method.
*/
console.log('\nExercise 3:');
function createPerson(name, age) {
// TODO: Make age private, accessible only via methods
// Provide: getName, getAge, birthday
}
// Test:
// const person = createPerson("Alice", 25);
// console.log(person.getName()); // "Alice"
// console.log(person.getAge()); // 25
// person.birthday();
// console.log(person.getAge()); // 26
// console.log(person.age); // undefined (private!)
/**
* Exercise 4: Function Factory
*
* Create a function that generates greeting functions.
*/
console.log('\nExercise 4:');
function createGreeter(greeting, punctuation) {
// TODO: Return function that takes name and returns greeting
}
// Test:
// const hello = createGreeter("Hello", "!");
// const goodbye = createGreeter("Goodbye", "...");
// console.log(hello("Alice")); // "Hello, Alice!"
// console.log(goodbye("Bob")); // "Goodbye, Bob..."
/**
* Exercise 5: Accumulator
*
* Create a function that returns a function which accumulates
* all the numbers passed to it.
*/
console.log('\nExercise 5:');
function createAccumulator(startValue) {
// TODO: Return function that adds to running total
}
// Test:
// const acc = createAccumulator(10);
// console.log(acc(5)); // 15
// console.log(acc(3)); // 18
// console.log(acc(2)); // 20
/**
* Exercise 6: Once Function
*
* Create a function that only runs once, subsequent calls
* return the first result.
*/
console.log('\nExercise 6:');
function once(fn) {
// TODO: Implement once wrapper
}
// Test:
// const initOnce = once((config) => {
// console.log("Initializing with:", config);
// return config.name;
// });
// console.log(initOnce({ name: "App1" })); // "Initializing..." then "App1"
// console.log(initOnce({ name: "App2" })); // Just "App1" (no re-init)
/**
* Exercise 7: Rate Limiter
*
* Create a function that limits how often another function
* can be called.
*/
console.log('\nExercise 7:');
function rateLimit(fn, limitMs) {
// TODO: Only allow fn to be called once per limitMs
}
// Test:
// const limited = rateLimit(() => {
// console.log("Called at", Date.now());
// }, 1000);
// limited(); // Executes
// limited(); // Ignored (too soon)
// setTimeout(limited, 1100); // Executes
/**
* Exercise 8: Loop Closure Fix
*
* Fix the loop so each function returns its own index.
*/
console.log('\nExercise 8:');
function createFunctions() {
const functions = [];
// TODO: Fix this loop using one of the closure solutions
for (var i = 0; i < 5; i++) {
functions.push(function () {
return i;
});
}
return functions;
}
// Test:
// const fns = createFunctions();
// console.log(fns[0]()); // Should be 0, not 5
// console.log(fns[2]()); // Should be 2, not 5
// console.log(fns[4]()); // Should be 4, not 5
/**
* Exercise 9: Memoization
*
* Create a memoization function that caches results.
*/
console.log('\nExercise 9:');
function memoize(fn) {
// TODO: Implement memoization
}
// Test:
// let callCount = 0;
// const expensive = memoize((n) => {
// callCount++;
// return n * 2;
// });
// console.log(expensive(5)); // 10 (computed)
// console.log(expensive(5)); // 10 (cached)
// console.log(expensive(10)); // 20 (computed)
// console.log("Call count:", callCount); // 2
/**
* Exercise 10: Module Pattern
*
* Create a task manager using the module pattern with
* private tasks array.
*/
console.log('\nExercise 10:');
const TaskManager = (function () {
// TODO: Implement with private tasks array
// Methods: addTask, removeTask, getTasks, clearTasks
})();
// Test:
// TaskManager.addTask("Learn closures");
// TaskManager.addTask("Practice JavaScript");
// console.log(TaskManager.getTasks()); // ["Learn closures", "Practice JavaScript"]
// TaskManager.removeTask("Learn closures");
// console.log(TaskManager.getTasks()); // ["Practice JavaScript"]
// console.log(TaskManager.tasks); // undefined (private!)
// =====================================================
// INTERMEDIATE EXERCISES
// =====================================================
/**
* Exercise 11: Curry Function
*
* Create a curry function that transforms a function to
* accept arguments one at a time.
*/
console.log('\nExercise 11:');
function curry(fn) {
// TODO: Return curried version of fn
}
// Test:
// function add(a, b, c) {
// return a + b + c;
// }
// const curriedAdd = curry(add);
// console.log(curriedAdd(1)(2)(3)); // 6
// console.log(curriedAdd(1, 2)(3)); // 6
// console.log(curriedAdd(1)(2, 3)); // 6
/**
* Exercise 12: Compose Functions
*
* Create a compose function that chains functions together.
*/
console.log('\nExercise 12:');
function compose(...fns) {
// TODO: Return function that composes fns right to left
}
// Test:
// const addOne = x => x + 1;
// const double = x => x * 2;
// const square = x => x * x;
// const transform = compose(addOne, double, square);
// console.log(transform(3)); // 3² = 9, *2 = 18, +1 = 19
/**
* Exercise 13: Event Emitter
*
* Create a simple event emitter with on, off, emit methods.
*/
console.log('\nExercise 13:');
function createEventEmitter() {
// TODO: Implement with closure
// Methods: on(event, handler), off(event, handler), emit(event, data)
}
// Test:
// const emitter = createEventEmitter();
// const handler = (data) => console.log("Received:", data);
// emitter.on("message", handler);
// emitter.emit("message", "Hello!"); // "Received: Hello!"
// emitter.off("message", handler);
// emitter.emit("message", "World!"); // Nothing (handler removed)
/**
* Exercise 14: Debounce Function
*
* Create a debounce function that delays execution.
*/
console.log('\nExercise 14:');
function debounce(fn, delay) {
// TODO: Implement debounce
}
// Test:
// const search = debounce((query) => {
// console.log("Searching:", query);
// }, 300);
// search("a");
// search("ab");
// search("abc");
// Only "abc" should be logged after 300ms
/**
* Exercise 15: Throttle Function
*
* Create a throttle function that limits execution rate.
*/
console.log('\nExercise 15:');
function throttle(fn, limit) {
// TODO: Implement throttle
}
// Test:
// let count = 0;
// const throttled = throttle(() => {
// count++;
// console.log("Executed:", count);
// }, 1000);
// throttled(); // Executes immediately
// throttled(); // Ignored
// throttled(); // Ignored
// setTimeout(throttled, 1100); // Executes
// =====================================================
// SOLUTIONS (Uncomment to check your answers)
// =====================================================
/*
// Exercise 1 Solution:
// Location A: globalVar, outerLet, blockConst, ifVar
// Location B: globalVar, outerLet, ifVar (blockConst is NOT accessible)
// Location C: globalVar, outerLet, innerLet (blockConst, ifVar not accessible)
// Location D: globalVar only
// Exercise 2 Solution:
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getValue() {
return count;
}
};
}
// Exercise 3 Solution:
function createPerson(name, initialAge) {
let age = initialAge;
return {
getName() {
return name;
},
getAge() {
return age;
},
birthday() {
age++;
return age;
}
};
}
// Exercise 4 Solution:
function createGreeter(greeting, punctuation) {
return function(name) {
return `${greeting}, ${name}${punctuation}`;
};
}
// Exercise 5 Solution:
function createAccumulator(startValue) {
let total = startValue;
return function(n) {
total += n;
return total;
};
}
// Exercise 6 Solution:
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
// Exercise 7 Solution:
function rateLimit(fn, limitMs) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= limitMs) {
lastCall = now;
return fn(...args);
}
};
}
// Exercise 8 Solution:
function createFunctions() {
const functions = [];
for (let i = 0; i < 5; i++) { // Changed var to let
functions.push(function() {
return i;
});
}
return functions;
}
// Exercise 9 Solution:
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key] !== undefined) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Exercise 10 Solution:
const TaskManager = (function() {
const tasks = [];
return {
addTask(task) {
tasks.push(task);
},
removeTask(task) {
const index = tasks.indexOf(task);
if (index > -1) {
tasks.splice(index, 1);
}
},
getTasks() {
return [...tasks];
},
clearTasks() {
tasks.length = 0;
}
};
})();
// Exercise 11 Solution:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return function(...moreArgs) {
return curried(...args, ...moreArgs);
};
};
}
// Exercise 12 Solution:
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
// Exercise 13 Solution:
function createEventEmitter() {
const events = {};
return {
on(event, handler) {
if (!events[event]) {
events[event] = [];
}
events[event].push(handler);
},
off(event, handler) {
if (events[event]) {
events[event] = events[event].filter(h => h !== handler);
}
},
emit(event, data) {
if (events[event]) {
events[event].forEach(handler => handler(data));
}
}
};
}
// Exercise 14 Solution:
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// Exercise 15 Solution:
function throttle(fn, limit) {
let lastCall = 0;
let timeoutId = null;
return function(...args) {
const now = Date.now();
const remaining = limit - (now - lastCall);
if (remaining <= 0) {
lastCall = now;
fn.apply(this, args);
} else if (!timeoutId) {
timeoutId = setTimeout(() => {
lastCall = Date.now();
timeoutId = null;
fn.apply(this, args);
}, remaining);
}
};
}
*/