Practice Questions
1. Write two functions: processNumbers(numbers, operation) that applies an operation to each element, and createMultiplier(factor) that returns a function multiplying numbers by that factor. What programming pattern do both functions demonstrate? Explain.
Solution
A higher-order function is a function that either takes one or more functions as arguments, returns a function, or both.
Taking a function as argument:
function processNumbers(numbers, operation) {
const results = [];
for (const num of numbers) {
results.push(operation(num));
}
return results;
}
const doubled = processNumbers([1, 2, 3], (x) => x * 2);
console.log(doubled); // [2, 4, 6]
Returning a function:
function createMultiplier(factor) {
return function (number) {
return number * factor;
};
}
const triple = createMultiplier(3);
console.log(triple(5)); // 15
2. Write a function createGreeter that takes a greeting string and returns a function that takes a name and returns a personalized greeting.
Solution
function createGreeter(greeting) {
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!"
Or using arrow functions:
const createGreeter = (greeting) => (name) => `${greeting}, ${name}!`;
3. Can inner functions access variables from their parent function? Can parent functions access variables from their inner functions? Explain with an example.
Solution
Inner functions can access variables from their parent function. Parent functions cannot access variables from their inner functions.
function outer() {
let outerVar = "I'm from outer";
function inner() {
let innerVar = "I'm from inner";
console.log(outerVar); // Works: "I'm from outer"
}
inner();
console.log(innerVar); // ReferenceError: innerVar is not defined
}
This happens because JavaScript’s scope chain lets an inner function access variables from outer scopes. It does not work the other way around.
4. Write a createCounter function that returns an object with increment, decrement, and getCount methods. The count variable should be local to createCounter. Using your implementation, explain what a closure is and why counter.count returns undefined.
Solution
A closure is created when a function continues to access variables from its enclosing scope, even after the outer function has finished executing.
function createCounter() {
let count = 0;
return {
increment() {
count++;
},
decrement() {
count--;
},
getCount() {
return count;
},
};
}
const counter = createCounter();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined (private)
The returned methods enclose the count variable. That makes it private, so the only way to read or change it is through those methods.
5. What will this code output? Explain why.
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
Solution
This outputs: 3, 3, 3
The var declaration creates a single i variable that is shared across all iterations. By the time the setTimeout callbacks execute (after 100ms), the loop has already finished and i equals 3.
To fix this, use let instead of var:
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
// Outputs: 0, 1, 2
With let, each iteration creates a new binding of i, so each callback captures its own value.
6. Transform the following function into a curried version, then use it to create a specialized addTax function that adds 8% tax.
function calculateTotal(taxRate, price) {
return price + price * taxRate;
}
Solution
function calculateTotal(taxRate) {
return function (price) {
return price + price * taxRate;
};
}
const addTax = calculateTotal(0.08);
console.log(addTax(100)); // 108
console.log(addTax(50)); // 54
Or using arrow functions:
const calculateTotal = (taxRate) => (price) => price + price * taxRate;
const addTax = calculateTotal(0.08);
7. Given these three functions, use composition to create a processEmail function that trims whitespace, converts to lowercase, and validates the email format (returns the email if valid, or “invalid” if not).
function trim(str) {
return str.trim();
}
function lowercase(str) {
return str.toLowerCase();
}
function validateEmail(str) {
return str.includes("@") ? str : "invalid";
}
Solution
function composeTwo(f, g) {
return function (x) {
return f(g(x));
};
}
const processEmail = composeTwo(validateEmail, composeTwo(lowercase, trim));
console.log(processEmail(" ALICE@EXAMPLE.COM ")); // "alice@example.com"
console.log(processEmail(" NOT-AN-EMAIL ")); // "invalid"
Or using a general compose utility:
function compose(...funcs) {
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args)),
);
}
const processEmail = compose(validateEmail, lowercase, trim);
8. What is the difference between a pure function and an impure function? Identify which of the following functions are pure and which are impure.
function add(a, b) {
return a + b;
}
let total = 0;
function addToTotal(value) {
total += value;
return total;
}
function getRandomMultiple(n) {
return n * Math.random();
}
function formatName(first, last) {
return `${first} ${last}`;
}
Solution
A pure function:
- Always returns the same output for the same input
- Has no side effects (does not modify external state)
An impure function violates one or both of these properties.
Analysis:
add(a, b)- Pure: same inputs always produce same output, no side effectsaddToTotal(value)- Impure: modifies external state (total), returns different values across callsgetRandomMultiple(n)- Impure: returns different outputs for the same input due toMath.random()formatName(first, last)- Pure: same inputs always produce same output, no side effects
9. How do you create an immutable update to an object in JavaScript? Given this object, write code that creates a new object with the age updated to 31 without mutating the original.
const person = { name: "John", age: 30, city: "Boston" };
Solution
Use the spread operator to create a new object with the updated property:
const person = { name: "John", age: 30, city: "Boston" };
const updatedPerson = { ...person, age: 31 };
console.log(person.age); // 30 (original unchanged)
console.log(updatedPerson.age); // 31
console.log(updatedPerson.name); // "John" (copied from original)
Note: const only prevents reassignment, not mutation. You could still do person.age = 31 (mutation). Spreading into a new object avoids the mutation, because the original object is never changed.
10. Write a once function 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.
Solution
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
const initialize = once(() => {
console.log("Initializing...");
return "initialized";
});
console.log(initialize()); // Logs "Initializing...", returns "initialized"
console.log(initialize()); // Returns "initialized" (no log)
console.log(initialize()); // Returns "initialized" (no log)
This uses a closure to track whether the function has been called and to store the result.