javascript

exercises

exercises.js
/**
 * =====================================================
 * 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);
        }
    };
}
*/
Exercises - JavaScript Tutorial | DeepML