5.7-Higher-Order-Functions
5.7 Higher-Order Functions
Overview
Higher-order functions are functions that either take other functions as arguments or return functions as their result. They're a fundamental concept in functional programming and are essential for writing clean, reusable, and expressive JavaScript code.
Table of Contents
- ā¢What are Higher-Order Functions?
- ā¢Functions as Arguments
- ā¢Functions as Return Values
- ā¢Built-in Array Higher-Order Functions
- ā¢map()
- ā¢filter()
- ā¢reduce()
- ā¢forEach()
- ā¢find() and findIndex()
- ā¢some() and every()
- ā¢sort()
- ā¢Chaining Methods
- ā¢Creating Custom Higher-Order Functions
- ā¢Best Practices
What are Higher-Order Functions?
A function is "higher-order" if it:
- ā¢Takes one or more functions as arguments, OR
- ā¢Returns a function as its result
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā HIGHER-ORDER FUNCTIONS ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Regular Function: ā
ā f(value) ā result ā
ā ā
ā Higher-Order (takes function): ā
ā f(function) ā result ā
ā ā
ā Higher-Order (returns function): ā
ā f(value) ā function ā
ā ā
ā Both: ā
ā f(function) ā function ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Basic Example
// Takes a function as argument
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log); // 0, 1, 2
// Returns a function
function greaterThan(n) {
return function (m) {
return m > n;
};
}
const greaterThan10 = greaterThan(10);
console.log(greaterThan10(15)); // true
Functions as Arguments
Passing functions to other functions enables powerful patterns.
Callback Pattern
function fetchData(callback) {
// Simulate async operation
setTimeout(() => {
const data = { id: 1, name: 'Result' };
callback(data);
}, 100);
}
fetchData((data) => {
console.log('Received:', data);
});
Customizable Behavior
function processArray(arr, operation) {
const result = [];
for (const item of arr) {
result.push(operation(item));
}
return result;
}
const numbers = [1, 2, 3, 4, 5];
const doubled = processArray(numbers, (n) => n * 2);
const squared = processArray(numbers, (n) => n * n);
const stringified = processArray(numbers, (n) => String(n));
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(squared); // [1, 4, 9, 16, 25]
console.log(stringified); // ["1", "2", "3", "4", "5"]
Functions as Return Values
Returning functions creates flexible, reusable code.
Function Factory
function createMultiplier(factor) {
return function (number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Configuration Pattern
function createValidator(rules) {
return function (value) {
for (const rule of rules) {
if (!rule.test(value)) {
return { valid: false, message: rule.message };
}
}
return { valid: true };
};
}
const validatePassword = createValidator([
{ test: (v) => v.length >= 8, message: 'Too short' },
{ test: (v) => /[A-Z]/.test(v), message: 'Need uppercase' },
{ test: (v) => /[0-9]/.test(v), message: 'Need number' },
]);
console.log(validatePassword('Abc12345')); // { valid: true }
console.log(validatePassword('abc')); // { valid: false, message: "Too short" }
Built-in Array Higher-Order Functions
JavaScript arrays come with many built-in higher-order methods.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ARRAY HIGHER-ORDER METHODS ā
āāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Method ā Description ā
āāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā map() ā Transform each element ā
ā filter() ā Keep elements matching condition ā
ā reduce() ā Combine elements into single value ā
ā forEach() ā Execute for each (no return) ā
ā find() ā First element matching condition ā
ā findIndex()ā Index of first match ā
ā some() ā At least one matches? ā
ā every() ā All match? ā
ā sort() ā Sort with comparison function ā
ā flatMap() ā Map then flatten one level ā
āāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
map()
Creates a new array by transforming each element.
Syntax
array.map(callback(element, index, array));
Examples
const numbers = [1, 2, 3, 4, 5];
// Double each number
const doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Extract property from objects
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 },
];
const names = users.map((user) => user.name);
console.log(names); // ["Alice", "Bob", "Charlie"]
// Using index
const indexed = numbers.map((num, index) => `${index}: ${num}`);
console.log(indexed); // ["0: 1", "1: 2", "2: 3", "3: 4", "4: 5"]
Important Notes
- ā¢Returns a new array (doesn't modify original)
- ā¢Same length as original array
- ā¢Use when you need to transform each element
filter()
Creates a new array with elements that pass a test.
Syntax
array.filter(callback(element, index, array));
Examples
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Even numbers
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// Greater than 5
const large = numbers.filter((n) => n > 5);
console.log(large); // [6, 7, 8, 9, 10]
// Filter objects
const users = [
{ name: 'Alice', active: true },
{ name: 'Bob', active: false },
{ name: 'Charlie', active: true },
];
const activeUsers = users.filter((user) => user.active);
console.log(activeUsers); // [{ name: "Alice", active: true }, { name: "Charlie", active: true }]
// Multiple conditions
const filtered = numbers.filter((n) => n > 3 && n < 8);
console.log(filtered); // [4, 5, 6, 7]
Important Notes
- ā¢Returns a new array
- ā¢Length may be less than original
- ā¢Use when you need to select a subset
reduce()
Combines all elements into a single value.
Syntax
array.reduce(callback(accumulator, current, index, array), initialValue);
Visual Representation
[1, 2, 3, 4, 5].reduce((acc, cur) => acc + cur, 0)
Step 1: acc=0, cur=1 ā 0+1 = 1
Step 2: acc=1, cur=2 ā 1+2 = 3
Step 3: acc=3, cur=3 ā 3+3 = 6
Step 4: acc=6, cur=4 ā 6+4 = 10
Step 5: acc=10, cur=5 ā 10+5 = 15
Result: 15
Examples
const numbers = [1, 2, 3, 4, 5];
// Sum
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// Product
const product = numbers.reduce((acc, n) => acc * n, 1);
console.log(product); // 120
// Max value
const max = numbers.reduce((acc, n) => (n > acc ? n : acc), -Infinity);
console.log(max); // 5
// Count occurrences
const letters = ['a', 'b', 'a', 'c', 'b', 'a'];
const counts = letters.reduce((acc, letter) => {
acc[letter] = (acc[letter] || 0) + 1;
return acc;
}, {});
console.log(counts); // { a: 3, b: 2, c: 1 }
// Flatten array
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5]
// Group by property
const people = [
{ name: 'Alice', dept: 'Engineering' },
{ name: 'Bob', dept: 'Sales' },
{ name: 'Charlie', dept: 'Engineering' },
];
const byDept = people.reduce((acc, person) => {
const dept = person.dept;
acc[dept] = acc[dept] || [];
acc[dept].push(person);
return acc;
}, {});
console.log(byDept);
Important Notes
- ā¢Always provide an initial value
- ā¢Can produce any type of result (number, object, array, etc.)
- ā¢Most powerful but can be harder to read
forEach()
Executes a function for each element (no return value).
Syntax
array.forEach(callback(element, index, array));
Examples
const numbers = [1, 2, 3, 4, 5];
// Side effect: logging
numbers.forEach((n) => console.log(n));
// Modifying external variable
let total = 0;
numbers.forEach((n) => {
total += n;
});
console.log(total); // 15
// With index
numbers.forEach((n, i) => {
console.log(`Index ${i}: ${n}`);
});
Important Notes
- ā¢Returns
undefined - ā¢Cannot break out of loop (use regular
forif needed) - ā¢Best for side effects (logging, DOM updates)
forEach vs map
// Use forEach for side effects
users.forEach((user) => {
sendEmail(user);
});
// Use map for transformation
const emails = users.map((user) => user.email);
find() and findIndex()
Find the first matching element or its index.
Syntax
array.find(callback(element, index, array));
array.findIndex(callback(element, index, array));
Examples
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
];
// find() - returns element
const user = users.find((u) => u.id === 2);
console.log(user); // { id: 2, name: "Bob" }
// findIndex() - returns index
const index = users.findIndex((u) => u.id === 2);
console.log(index); // 1
// Not found
const notFound = users.find((u) => u.id === 99);
console.log(notFound); // undefined
const notFoundIndex = users.findIndex((u) => u.id === 99);
console.log(notFoundIndex); // -1
find vs filter
// find - returns first match only
const first = numbers.find((n) => n > 3); // 4
// filter - returns all matches
const all = numbers.filter((n) => n > 3); // [4, 5, 6, ...]
some() and every()
Test if elements match a condition.
Syntax
array.some(callback(element, index, array)); // At least one?
array.every(callback(element, index, array)); // All?
Examples
const numbers = [1, 2, 3, 4, 5];
// some() - at least one matches
console.log(numbers.some((n) => n > 3)); // true
console.log(numbers.some((n) => n > 10)); // false
// every() - all match
console.log(numbers.every((n) => n > 0)); // true
console.log(numbers.every((n) => n > 3)); // false
// Practical example
const users = [
{ name: 'Alice', verified: true },
{ name: 'Bob', verified: true },
{ name: 'Charlie', verified: false },
];
const allVerified = users.every((u) => u.verified);
console.log(allVerified); // false
const anyVerified = users.some((u) => u.verified);
console.log(anyVerified); // true
Short-Circuit Behavior
// some() stops at first true
[1, 2, 3].some((n) => {
console.log('Checking', n);
return n === 2;
});
// Logs: "Checking 1", "Checking 2" (stops)
// every() stops at first false
[1, 2, 3].every((n) => {
console.log('Checking', n);
return n < 2;
});
// Logs: "Checking 1", "Checking 2" (stops)
sort()
Sorts array in place using a comparison function.
Syntax
array.sort(compareFunction(a, b));
Comparison Function Rules
compareFunction(a, b):
< 0 ā a comes before b
= 0 ā keep original order
> 0 ā b comes before a
Examples
const numbers = [3, 1, 4, 1, 5, 9, 2, 6];
// Ascending (numeric)
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 1, 2, 3, 4, 5, 6, 9]
// Descending (numeric)
numbers.sort((a, b) => b - a);
console.log(numbers); // [9, 6, 5, 4, 3, 2, 1, 1]
// Strings (alphabetical)
const names = ['Charlie', 'Alice', 'Bob'];
names.sort(); // Default alphabetical
console.log(names); // ["Alice", "Bob", "Charlie"]
// Case-insensitive
const mixed = ['banana', 'Apple', 'cherry'];
mixed.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(mixed); // ["Apple", "banana", "cherry"]
// Sort by object property
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 },
];
users.sort((a, b) => a.age - b.age);
console.log(users); // Bob, Alice, Charlie
Important Notes
- ā¢Modifies original array
- ā¢Default sort converts to strings
- ā¢Always use compare function for numbers
Chaining Methods
Combine methods for complex operations.
const transactions = [
{ type: 'sale', amount: 100 },
{ type: 'refund', amount: -50 },
{ type: 'sale', amount: 200 },
{ type: 'sale', amount: 75 },
{ type: 'refund', amount: -25 },
];
// Get total of sales over $50
const result = transactions
.filter((t) => t.type === 'sale') // Only sales
.filter((t) => t.amount > 50) // Over $50
.map((t) => t.amount) // Get amounts
.reduce((sum, a) => sum + a, 0); // Sum them
console.log(result); // 300
// Find users, extract and format names
const users = [
{ name: 'alice', active: true },
{ name: 'bob', active: false },
{ name: 'charlie', active: true },
];
const activeNames = users
.filter((u) => u.active)
.map((u) => u.name)
.map((name) => name.charAt(0).toUpperCase() + name.slice(1))
.join(', ');
console.log(activeNames); // "Alice, Charlie"
Chain Visualization
transactions
ā
ā¼ filter(type === 'sale')
[sale:100, sale:200, sale:75]
ā
ā¼ filter(amount > 50)
[sale:100, sale:200, sale:75]
ā
ā¼ map(amount)
[100, 200, 75]
ā
ā¼ reduce(sum)
375
Creating Custom Higher-Order Functions
Custom map
function myMap(array, transform) {
const result = [];
for (const item of array) {
result.push(transform(item));
}
return result;
}
myMap([1, 2, 3], (n) => n * 2); // [2, 4, 6]
Custom filter
function myFilter(array, predicate) {
const result = [];
for (const item of array) {
if (predicate(item)) {
result.push(item);
}
}
return result;
}
myFilter([1, 2, 3, 4], (n) => n % 2 === 0); // [2, 4]
Custom reduce
function myReduce(array, reducer, initial) {
let accumulator = initial;
for (const item of array) {
accumulator = reducer(accumulator, item);
}
return accumulator;
}
myReduce([1, 2, 3], (a, b) => a + b, 0); // 6
Pipe Function
function pipe(...fns) {
return function (value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}
const process = pipe(
(n) => n * 2,
(n) => n + 1,
(n) => n * 3
);
console.log(process(5)); // ((5 * 2) + 1) * 3 = 33
Best Practices
1. Use Descriptive Names
// ā Unclear
const x = users.filter((u) => u.a > 18);
// ā
Clear
const adultUsers = users.filter((user) => user.age > 18);
2. Prefer Method Chaining Over Loops
// ā Imperative
let result = [];
for (let i = 0; i < users.length; i++) {
if (users[i].active) {
result.push(users[i].name.toUpperCase());
}
}
// ā
Declarative
const result = users
.filter((user) => user.active)
.map((user) => user.name.toUpperCase());
3. Don't Modify Original Array
// ā Mutation
numbers.sort();
// ā
Copy first
const sorted = [...numbers].sort();
4. Use the Right Method
| Need | Use |
|---|---|
| Transform all | map() |
| Select some | filter() |
| Single value | reduce() |
| First match | find() |
| Existence check | some()/every() |
| Side effects | forEach() |
5. Keep Callbacks Pure
// ā Side effects in map
let total = 0;
numbers.map((n) => {
total += n; // Side effect!
return n * 2;
});
// ā
Use reduce for accumulation
const total = numbers.reduce((sum, n) => sum + n, 0);
const doubled = numbers.map((n) => n * 2);
Summary
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā HIGHER-ORDER FUNCTIONS ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā DEFINITION: ā
ā ⢠Takes function(s) as argument(s), OR ā
ā ⢠Returns a function ā
ā ā
ā KEY METHODS: ā
ā map() - Transform each ā new array ā
ā filter() - Select matching ā new array ā
ā reduce() - Combine all ā single value ā
ā forEach() - Execute for each ā undefined ā
ā find() - First match ā element ā
ā findIndex()- First match ā index ā
ā some() - Any match? ā boolean ā
ā every() - All match? ā boolean ā
ā sort() - Sort in place ā same array ā
ā ā
ā BENEFITS: ā
ā ⢠Declarative code ā
ā ⢠Reusable logic ā
ā ⢠Composable operations ā
ā ⢠Easier to reason about ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Next Steps
- ā¢Practice with the examples in
examples.js - ā¢Complete the exercises in
exercises.js - ā¢Explore functional programming concepts
- ā¢Learn about recursion and advanced patterns