In JavaScript, functions are first-class citizens, they can be assigned to variables, passed as arguments, and returned from other functions. Higher-order functions take advantage of this superpower. They're functions that operate on other functions, either by taking them as arguments or returning them.
What makes a function "higher-order"?
A higher-order function is any function that:
- Takes one or more functions as arguments, OR
- Returns a function as its result
You've already used higher-order functions: setTimeout, addEventListener, and array methods like map are all higher-order functions.
The big three array methods
Modern JavaScript provides three essential higher-order functions for working with arrays. Master these and you'll handle the vast majority of array transformations you'll need.
| Method | Purpose | Returns | Mutates original? |
|---|---|---|---|
map() | Transform every element | New array (same length) | No |
filter() | Keep elements that pass a test | New array (same or fewer) | No |
reduce() | Combine into one value | Any type | No |
map(): Transform every element
map creates a new array by applying a function to every element. It doesn't modify the original array.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10]
const names = ["alice", "bob", "charlie"];
const uppercased = names.map(name => name.toUpperCase());
// ["ALICE", "BOB", "CHARLIE"]The callbackWhat is callback?A function you pass into another function to be called later, often when an operation finishes or an event occurs. receives three arguments:
- The current element
- The current indexWhat is index?A data structure the database maintains alongside a table so it can find rows by specific columns quickly instead of scanning everything.
- The original array
const indexed = names.map((name, index) => `${index + 1}. ${name}`);
// ["1. alice", "2. bob", "3. charlie"]filter(): Keep what matches
filter creates a new array with only elements that pass a test:
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4, 6]
const greaterThanThree = numbers.filter(n => n > 3);
// [4, 5, 6]The callback should return true to keep the element, false to filter it out:
const users = [
{ name: "Alice", age: 25, active: true },
{ name: "Bob", age: 17, active: true },
{ name: "Charlie", age: 30, active: false }
];
const activeAdults = users.filter(user =>
user.active && user.age >= 18
);
// [{ name: "Alice", age: 25, active: true }]reduce(): Combine into one value
reduce takes an array and reduces it to a single value. It's the most powerful and most complex of the three.
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, current) => {
return accumulator + current;
}, 0);
// 15The reduce callback receives:
- accumulator: the running total (or whatever you're building)
- current: the current element being processed
- index: the current index (optional)
- array: the original array (optional)
The second argument to reduce (0 in the example) is the initial value for the accumulator.
.map().filter().reduce() three levels deep, or use reduce to build something that a simple for...of loop expresses more clearly. If you can't read an AI-generated chain in 10 seconds, refactor it. Readable code beats clever code every time.Practical reduce examples
Build an object from an array:
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
const userMap = users.reduce((map, user) => {
map[user.id] = user;
return map;
}, {});
// { 1: {id: 1, name: "Alice"}, 2: {...}, 3: {...} }
console.log(userMap[2].name); // "Bob"Group items by category:
const products = [
{ name: "Apple", category: "fruit" },
{ name: "Carrot", category: "vegetable" },
{ name: "Banana", category: "fruit" }
];
const grouped = products.reduce((groups, product) => {
const category = product.category;
if (!groups[category]) {
groups[category] = [];
}
groups[category].push(product);
return groups;
}, {});
// { fruit: [{...}, {...}], vegetable: [{...}] }Chaining array methods
These methods return arrays (except reduce), so you can chain them together:
const users = [
{ name: "Alice", age: 25, active: true },
{ name: "Bob", age: 17, active: true },
{ name: "Charlie", age: 30, active: false },
{ name: "Diana", age: 22, active: true }
];
// Get names of active adults, sorted by age
const activeAdultNames = users
.filter(u => u.active && u.age >= 18) // Keep active adults
.map(u => u.name); // Extract names
console.log(activeAdultNames); // ["Alice", "Diana"]reduce() or a for...of loop instead of long chains. For typical use cases (hundreds or thousands of items), chaining is perfectly fine and much more readable.Writing your own higher-order functions
You can create your own higher-order functions:
// Takes a callback and calls it
function withLogging(callback) {
console.log("Starting operation...");
const result = callback();
console.log("Operation complete!");
return result;
}
withLogging(() => {
console.log("Doing the work");
return 42;
});
// Logs:
// Starting operation...
// Doing the work
// Operation complete!Returning a function (function factory):
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}
const triple = multiplyBy(3);
const double = multiplyBy(2);
console.log(triple(5)); // 15
console.log(double(5)); // 10Quick reference
| Method | What it does | Returns |
|---|---|---|
map() | Transform each element | New array |
filter() | Keep matching elements | New array |
reduce() | Combine into one value | Any type |
forEach() | Run function for each element | undefined |
find() | First matching element | Element or undefined |
findIndex() | Index of first match | Index or -1 |
some() | Test if any element passes | true or false |
every() | Test if all elements pass | true or false |
const numbers = [1, 2, 3, 4, 5];
// map: transform each element
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10]
// filter: keep elements that pass a test
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4]
// reduce: combine into single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 15
// Chaining methods
const result = numbers
.filter(n => n > 2)
.map(n => n * 10);
// [30, 40, 50]
// find and some
console.log(numbers.find(n => n > 3)); // 4
console.log(numbers.some(n => n > 4)); // true