JavaScript Programming Labs

Master JavaScript through hands-on coding challenges. Write real code, get instant feedback, and build practical web development skills.

Closures, Higher-Order Functions & Recursion - Module 8

Master advanced JavaScript concepts: closures for data privacy, higher-order functions for abstraction, and recursion for elegant solutions.

Lab 22: Closures
Advanced
Coding Challenge
Your Task: Master closures - one of JavaScript's most powerful features. Closures allow functions to remember and access variables from their outer scope even after the outer function has returned.

Detailed Requirements:

1. Understand lexical scope - Functions can access variables from their parent scope.
function outer() { const message = "Hello from outer!"; function inner() { // inner() has access to 'message' from outer() console.log(message); } inner(); // "Hello from outer!" } outer();
2. Create a basic closure - Return a function that remembers its environment.
function createGreeter(greeting) { // 'greeting' is captured in the closure return function(name) { return greeting + ", " + name + "!"; }; } const sayHello = createGreeter("Hello"); const sayHi = createGreeter("Hi"); console.log(sayHello("Alice")); // "Hello, Alice!" console.log(sayHi("Bob")); // "Hi, Bob!"
3. Create a counter with private state - Use closures for data privacy.
function createCounter() { let count = 0; // Private variable return { increment: function() { count++; return count; }, decrement: function() { count--; return count; }, getCount: function() { return count; } }; } const counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.getCount()); // 2 // count is not directly accessible!
4. Create a function factory - Generate specialized functions.
function multiplier(factor) { return function(number) { return number * factor; }; } const double = multiplier(2); const triple = multiplier(3); const tenTimes = multiplier(10); console.log(double(5)); // 10 console.log(triple(5)); // 15 console.log(tenTimes(5)); // 50
5. Create a memoization function - Cache results for performance.
function memoize(fn) { const cache = {}; // Closure keeps cache private return function(arg) { if (cache[arg] !== undefined) { console.log("From cache:", arg); return cache[arg]; } console.log("Computing:", arg); const result = fn(arg); cache[arg] = result; return result; }; } const expensiveSquare = memoize(x => x * x); console.log(expensiveSquare(5)); // Computing: 5 → 25 console.log(expensiveSquare(5)); // From cache: 5 → 25
6. Use closure with setTimeout - Common closure pattern.
function delayedMessages() { for (let i = 1; i <= 3; i++) { // 'let' creates a new binding for each iteration setTimeout(function() { console.log("Message " + i); }, i * 1000); } } // Using 'let' (not 'var') ensures correct values // Alternative with IIFE (Immediately Invoked Function Expression) for (var i = 1; i <= 3; i++) { (function(num) { setTimeout(() => console.log("Num: " + num), num * 100); })(i); }
💡 Pro Tips:
• Closures capture variables by reference, not value
• Use closures for data privacy (module pattern)
• Be careful with closures in loops - use let or IIFE
• Closures keep variables in memory - be mindful of memory usage

⚠️ Common Mistakes to Avoid:
• Using var in loops with closures (captures final value)
• Creating unnecessary closures in performance-critical code
• Not understanding that closures capture by reference

Expected Output:
Greeting: Hello, World! Counter: 1, 2, 3 Double: 10, Triple: 15 Memoized result from cache

Requirements Checklist

Create a function that returns another function
Use a closure to access outer scope variables
Create private state with closure (counter pattern)
Create a function factory (multiplier, adder, etc.)
Implement memoization or caching with closure
Use closure with setTimeout or event handler
Console Output
// Click "Run Code" to execute your JavaScript
Hints & Tips
• A closure is created when a function is defined inside another function
• Return a function: return function() { ... }
• Private state: declare variables in outer function, access in inner
• Factory pattern: function make(x) { return (y) => x + y; }
• Memoization: use an object as cache inside closure
• Use let in loops with setTimeout to capture correct values
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below

Lab 23: Higher-Order Functions
Advanced
Coding Challenge
Your Task: Master higher-order functions (HOFs) - functions that take functions as arguments or return functions. These are fundamental to functional programming in JavaScript.

Detailed Requirements:

1. Create a function that takes a callback
function doOperation(a, b, operation) { return operation(a, b); } const add = (x, y) => x + y; const multiply = (x, y) => x * y; console.log(doOperation(5, 3, add)); // 8 console.log(doOperation(5, 3, multiply)); // 15 // With anonymous function console.log(doOperation(10, 2, (x, y) => x / y)); // 5
2. Create a function that returns a function
function createValidator(minLength) { return function(str) { return str.length >= minLength; }; } const isValidPassword = createValidator(8); const isValidUsername = createValidator(3); console.log(isValidPassword("abc")); // false console.log(isValidPassword("secure123")); // true console.log(isValidUsername("Jo")); // false console.log(isValidUsername("John")); // true
3. Implement your own map function
function myMap(array, transformFn) { const result = []; for (const item of array) { result.push(transformFn(item)); } return result; } const numbers = [1, 2, 3, 4, 5]; const doubled = myMap(numbers, x => x * 2); console.log(doubled); // [2, 4, 6, 8, 10] const names = ["alice", "bob"]; const upper = myMap(names, s => s.toUpperCase()); console.log(upper); // ["ALICE", "BOB"]
4. Implement your own filter function
function myFilter(array, predicateFn) { const result = []; for (const item of array) { if (predicateFn(item)) { result.push(item); } } return result; } const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const evens = myFilter(nums, n => n % 2 === 0); console.log(evens); // [2, 4, 6, 8, 10] const words = ["apple", "banana", "cherry", "date"]; const longWords = myFilter(words, w => w.length > 5); console.log(longWords); // ["banana", "cherry"]
5. Implement your own reduce function
function myReduce(array, reducerFn, initialValue) { let accumulator = initialValue; for (const item of array) { accumulator = reducerFn(accumulator, item); } return accumulator; } const numbers = [1, 2, 3, 4, 5]; const sum = myReduce(numbers, (acc, n) => acc + n, 0); console.log("Sum:", sum); // 15 const product = myReduce(numbers, (acc, n) => acc * n, 1); console.log("Product:", product); // 120 const max = myReduce(numbers, (acc, n) => n > acc ? n : acc, -Infinity); console.log("Max:", max); // 5
6. Create a function composition utility
function compose(...fns) { return function(x) { return fns.reduceRight((acc, fn) => fn(acc), x); }; } // Or pipe (left to right) function pipe(...fns) { return function(x) { return fns.reduce((acc, fn) => fn(acc), x); }; } const addOne = x => x + 1; const double = x => x * 2; const square = x => x * x; const composed = compose(square, double, addOne); console.log(composed(3)); // square(double(addOne(3))) = square(8) = 64 const piped = pipe(addOne, double, square); console.log(piped(3)); // square(double(addOne(3))) = 64
💡 Pro Tips:
• HOFs enable code reuse and abstraction
• Use arrow functions for concise callbacks
• Composition creates complex behavior from simple functions
• Built-in HOFs: map, filter, reduce, forEach, find, some, every

⚠️ Common Mistakes to Avoid:
• Forgetting to return from callbacks
• Mutating the original array in HOFs
• Not providing initial value to reduce when needed

Expected Output:
Operation result: 8 Validator: true/false myMap: [2, 4, 6, 8, 10] myFilter: [2, 4, 6, 8, 10] myReduce sum: 15

Requirements Checklist

Create a function that takes a callback parameter
Create a function that returns another function
Implement a custom map function
Implement a custom filter function
Implement a custom reduce function
Create a compose or pipe function
Console Output
// Click "Run Code" to execute your JavaScript
Hints & Tips
• Callback: function doX(callback) { callback(); }
• Return function: function make() { return () => {}; }
• myMap: loop array, push transformFn(item) to result
• myFilter: loop array, push item if predicateFn(item) is true
• myReduce: loop array, update acc = reducerFn(acc, item)
• Compose: fns.reduceRight((acc, fn) => fn(acc), x)
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below

Lab 24: Recursion
Advanced
Coding Challenge
Your Task: Master recursion - a technique where a function calls itself to solve smaller instances of the same problem. Essential for tree traversal, mathematical sequences, and elegant problem-solving.

Detailed Requirements:

1. Implement factorial recursively - The classic example.
function factorial(n) { // Base case: factorial of 0 or 1 is 1 if (n <= 1) { return 1; } // Recursive case: n! = n * (n-1)! return n * factorial(n - 1); } console.log(factorial(0)); // 1 console.log(factorial(1)); // 1 console.log(factorial(5)); // 120 (5 * 4 * 3 * 2 * 1) console.log(factorial(10)); // 3628800
2. Implement Fibonacci sequence
function fibonacci(n) { // Base cases if (n <= 0) return 0; if (n === 1) return 1; // Recursive case: fib(n) = fib(n-1) + fib(n-2) return fibonacci(n - 1) + fibonacci(n - 2); } console.log(fibonacci(0)); // 0 console.log(fibonacci(1)); // 1 console.log(fibonacci(6)); // 8 (0,1,1,2,3,5,8) console.log(fibonacci(10)); // 55 // More efficient with memoization function fibMemo(n, memo = {}) { if (n in memo) return memo[n]; if (n <= 1) return n; memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo); return memo[n]; }
3. Sum an array recursively
function sumArray(arr) { // Base case: empty array if (arr.length === 0) { return 0; } // Recursive case: first element + sum of rest return arr[0] + sumArray(arr.slice(1)); } console.log(sumArray([1, 2, 3, 4, 5])); // 15 console.log(sumArray([10, 20, 30])); // 60 console.log(sumArray([])); // 0
4. Reverse a string recursively
function reverseString(str) { // Base case: empty or single char if (str.length <= 1) { return str; } // Recursive: last char + reverse of rest return str[str.length - 1] + reverseString(str.slice(0, -1)); } console.log(reverseString("hello")); // "olleh" console.log(reverseString("JavaScript")); // "tpircSavaJ" console.log(reverseString("a")); // "a"
5. Flatten a nested array
function flatten(arr) { let result = []; for (const item of arr) { if (Array.isArray(item)) { // Recursive case: flatten nested array result = result.concat(flatten(item)); } else { // Base case: add non-array item result.push(item); } } return result; } console.log(flatten([1, [2, 3], [4, [5, 6]]])); // [1, 2, 3, 4, 5, 6] console.log(flatten([[1, 2], [[3]], [4, [5, [6]]]])); // [1, 2, 3, 4, 5, 6]
6. Deep clone an object recursively
function deepClone(obj) { // Base case: primitives if (obj === null || typeof obj !== "object") { return obj; } // Handle arrays if (Array.isArray(obj)) { return obj.map(item => deepClone(item)); } // Handle objects const cloned = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = deepClone(obj[key]); } } return cloned; } const original = { a: 1, b: { c: 2, d: [3, 4] } }; const clone = deepClone(original); clone.b.c = 99; console.log(original.b.c); // 2 (unchanged!)
💡 Pro Tips:
• Every recursion needs a base case to stop
• Each recursive call should move toward the base case
• Use memoization for performance (Fibonacci, etc.)
• Be aware of stack overflow for deep recursion
• Tail recursion optimization not guaranteed in JS

⚠️ Common Mistakes to Avoid:
• Forgetting the base case (infinite recursion)
• Not moving toward the base case
• Stack overflow from too many recursive calls

Expected Output:
factorial(5) = 120 fibonacci(10) = 55 sumArray([1,2,3,4,5]) = 15 reverseString("hello") = "olleh" flatten result: [1, 2, 3, 4, 5, 6]

Requirements Checklist

Implement factorial function recursively
Implement Fibonacci sequence recursively
Sum an array recursively
Reverse a string recursively
Flatten a nested array recursively
Implement deep clone or tree traversal
Console Output
// Click "Run Code" to execute your JavaScript
Hints & Tips
• Base case: if (n <= 1) return 1;
• Factorial: n * factorial(n - 1)
• Fibonacci: fib(n-1) + fib(n-2)
• Array sum: arr[0] + sumArray(arr.slice(1))
• Reverse: lastChar + reverse(rest)
• Flatten: check Array.isArray(item)
Progress: 0/6
Score: 0/100
0%

Lab Results

Review feedback below