What Is a Higher-Order Function?
A higher-order function (HOF) is a function that either:
- Takes one or more functions as arguments (callbacks), or
- Returns a function as its result, or
- Both
// HOF: takes a function as argument
function applyToArray(arr, fn) {
return arr.map(fn);
}
const doubled = applyToArray([1, 2, 3, 4], n => n * 2);
console.log(doubled); // [2, 4, 6, 8]
// HOF: returns a function
function createMultiplier(factor) {
return n => n * factor; // Returns a function
}
const times5 = createMultiplier(5);
console.log(times5(6)); // 30
Array.map()
map() transforms every element of an array and returns a new array of the same length. The original array is not mutated.
const prices = [10, 25, 50, 100];
// Apply tax
const pricesWithTax = prices.map(p => p * 1.2);
console.log(pricesWithTax); // [12, 30, 60, 120]
// Extract property from objects
const users = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
{ id: 3, name: "Carol", age: 35 }
];
const names = users.map(u => u.name);
console.log(names); // ["Alice", "Bob", "Carol"]
// Map with index
const indexed = users.map((u, i) => `${i + 1}. ${u.name}`);
console.log(indexed); // ["1. Alice", "2. Bob", "3. Carol"]
// Transform to a different shape
const userCards = users.map(({ id, name }) => ({
key: id,
display: name.toUpperCase()
}));
console.log(userCards);
Array.filter()
filter() keeps only elements for which the callback returns truthy. Returns a new (potentially shorter) array.
const products = [
{ name: "Laptop", price: 999, inStock: true },
{ name: "Mouse", price: 29, inStock: false },
{ name: "Desk", price: 350, inStock: true },
{ name: "Chair", price: 150, inStock: true },
{ name: "Monitor",price: 400, inStock: false }
];
// In-stock products under $400
const affordable = products
.filter(p => p.inStock)
.filter(p => p.price < 400);
console.log(affordable.map(p => p.name)); // ["Chair"]
// Multiple conditions in one filter
const available = products.filter(p => p.inStock && p.price <= 999);
console.log(available.map(p => p.name)); // ["Laptop", "Desk", "Chair"]
// Remove falsy values
const mixed = [0, 1, "", "hello", null, "world", undefined, 42];
const truthy = mixed.filter(Boolean);
console.log(truthy); // [1, "hello", "world", 42]
Array.reduce()
reduce() accumulates array elements into a single value. It is the most powerful array method β map and filter can be implemented with reduce.
const orders = [
{ product: "Book", qty: 3, price: 15 },
{ product: "Pen", qty: 10, price: 2 },
{ product: "Bag", qty: 1, price: 45 }
];
// Sum total
const total = orders.reduce((acc, order) => acc + order.qty * order.price, 0);
console.log("Total: $" + total); // Total: $110
// Group by (transform array into object)
const inventory = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const counts = inventory.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(counts); // { apple: 3, banana: 2, cherry: 1 }
// Flatten nested array
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5, 6]
Chaining map, filter, reduce
const employees = [
{ name: "Alice", department: "Engineering", salary: 90000 },
{ name: "Bob", department: "Marketing", salary: 75000 },
{ name: "Carol", department: "Engineering", salary: 95000 },
{ name: "Dave", department: "Engineering", salary: 80000 },
{ name: "Eve", department: "Marketing", salary: 70000 }
];
// Total salary for Engineering department
const engTotal = employees
.filter(e => e.department === "Engineering")
.map(e => e.salary)
.reduce((sum, s) => sum + s, 0);
console.log("Engineering total salary: $" + engTotal); // $265,000
// Average salary across all departments
const avg = employees
.map(e => e.salary)
.reduce((sum, s, _, arr) => sum + s / arr.length, 0);
console.log("Average salary: $" + avg.toFixed(0)); // $82,000
forEach vs map
| Feature | forEach | map |
|---|---|---|
| Returns | undefined | New array |
| Purpose | Side effects (logging, DOM updates) | Transforming values |
| Chainable | No | Yes |
| Mutates original | Can (you control it) | No |
| Can break/continue | No | No |
find() and findIndex()
const users = [
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Carol", active: true }
];
// find β returns the first matching element (or undefined)
const bob = users.find(u => u.id === 2);
console.log(bob); // { id: 2, name: 'Bob', active: false }
// findIndex β returns the index of the first match (-1 if not found)
const carolIdx = users.findIndex(u => u.name === "Carol");
console.log(carolIdx); // 2
// Practical: find and update
const idx = users.findIndex(u => u.id === 2);
if (idx !== -1) {
users[idx] = { ...users[idx], active: true }; // Immutable update
}
console.log(users[1].active); // true
some() and every()
const scores = [78, 92, 55, 88, 45, 97];
// some β true if at least one element passes
const hasFailure = scores.some(s => s < 60);
console.log("Has failure:", hasFailure); // true
const hasPerfect = scores.some(s => s === 100);
console.log("Has perfect:", hasPerfect); // false
// every β true only if ALL elements pass
const allPassed = scores.every(s => s >= 40);
console.log("All passed (>=40):", allPassed); // true
const allExcellent = scores.every(s => s >= 90);
console.log("All excellent:", allExcellent); // false
Custom Higher-Order Functions
// repeat β run a function n times
function repeat(n, fn) {
for (let i = 0; i < n; i++) fn(i);
}
repeat(3, i => console.log("Iteration " + i));
// once β a function that can only be called once
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
const initOnce = once(() => {
console.log("Initialized!");
return 42;
});
console.log(initOnce()); // Initialized! then 42
console.log(initOnce()); // 42 β no "Initialized!" again
Compose and Pipe Pattern
compose chains functions right-to-left; pipe chains them left-to-right (more intuitive).
// pipe: applies functions left to right
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
// compose: applies functions right to left
const compose = (...fns) => (value) => fns.reduceRight((acc, fn) => fn(acc), value);
// Individual transformations
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const removeSpaces = str => str.replace(/\s+/g, "-");
// Pipe: left to right (trim β lowercase β replace spaces)
const slugify = pipe(trim, toLowerCase, removeSpaces);
console.log(slugify(" Hello World ")); // "hello-world"
console.log(slugify(" Learn JavaScript! ")); // "learn-javascript!"
// Numeric pipeline
const processNumber = pipe(
n => n * 2, // double
n => n + 10, // add 10
n => n ** 2 // square
);
console.log(processNumber(3)); // (3*2 + 10)^2 = 16^2 = 256
ποΈ Practical Exercise
Given this dataset of transactions:
const transactions = [
{ id: 1, type: "credit", amount: 500 },
{ id: 2, type: "debit", amount: 120 },
{ id: 3, type: "credit", amount: 1000 },
{ id: 4, type: "debit", amount: 350 },
{ id: 5, type: "credit", amount: 200 }
];
Use map, filter, and reduce to find:
- Total credits
- Total debits
- Net balance (credits β debits)
- List of credit transaction IDs
π₯ Challenge Exercise
Implement these higher-order functions from scratch (without using the built-in versions):
myMap(arr, fn)myFilter(arr, fn)myReduce(arr, fn, initial)
Then create a groupBy(arr, keyFn) function that groups array elements into an object by the result of keyFn. Use reduce internally.
π Summary
- Higher-order functions accept functions as arguments or return functions.
map()transforms every element β returns same-length array.filter()keeps matching elements β returns shorter array.reduce()accumulates to one value β most powerful of the three.- Chain map/filter/reduce for readable data pipelines.
some()/every()check array-level boolean conditions efficiently.find()/findIndex()locate the first matching element.- Compose and pipe let you build transformation pipelines from simple functions.
Interview Questions
- What is a higher-order function? Give two real examples.
- Explain the difference between map() and forEach().
- How does Array.reduce() work? What is the accumulator?
- When would you use find() vs filter()?
- What is the compose/pipe pattern and why is it useful?
Frequently Asked Questions
No. map(), filter(), and reduce() do not mutate the original array β they return new arrays (or a new value). However, if the callback mutates objects inside the array, those objects are shared by reference and can be modified.
It throws a TypeError: Reduce of empty array with no initial value. Always provide an initial value (the second argument to reduce) when the array might be empty.
Yes β both map and filter can be implemented using reduce. reduce is essentially a general-purpose array-to-anything function. However, using map and filter when applicable is more readable and semantically clear.