Higher-order functions are functions that operate on other functions— either by taking them as arguments or returning them. Callbacks are functions passed as arguments to be executed later. These concepts are fundamental to functional programming and are used extensively in modern JavaScript, from array methods to asynchronous operations. Let's master these powerful patterns!
First-Class Functions
In JavaScript, functions are "first-class citizens"—they can be treated like any other value.
Functions can be:
- Assigned to variables
- Passed as arguments to other functions
- Returned from other functions
- Stored in data structures (arrays, objects)
// 1. Assign to variable
const greet = function(name) {
return `Hello, ${name}!`;
};
console.log(greet("Alice")); // "Hello, Alice!"
// 2. Store in array
const operations = [
function(x) { return x + 1; },
function(x) { return x * 2; },
function(x) { return x ** 2; }
];
console.log(operations[0](5)); // 6
console.log(operations[1](5)); // 10
console.log(operations[2](5)); // 25
// 3. Store in object
const calculator = {
add: function(a, b) { return a + b; },
subtract: function(a, b) { return a - b; },
multiply: function(a, b) { return a * b; }
};
console.log(calculator.add(5, 3)); // 8
// 4. Pass as argument (covered next)
// 5. Return from function (covered next)Higher-Order Functions
A higher-order function is a function that either:
- Accepts one or more functions as arguments, OR
- Returns a function as its result
Functions That Accept Functions
// Higher-order function: accepts a function as argument
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
// Pass function as argument
repeat(3, function(i) {
console.log(`Iteration ${i}`);
});
// Output:
// Iteration 0
// Iteration 1
// Iteration 2
// With arrow function
repeat(3, i => console.log(`Number: ${i}`));
// Another example
function processArray(arr, processor) {
let results = [];
for (let item of arr) {
results.push(processor(item));
}
return results;
}
let numbers = [1, 2, 3, 4, 5];
let doubled = processArray(numbers, x => x * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
let squared = processArray(numbers, x => x ** 2);
console.log(squared); // [1, 4, 9, 16, 25]Functions That Return Functions
// Higher-order function: returns a function
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// More practical example
function createGreeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
let sayHello = createGreeter("Hello");
let sayHi = createGreeter("Hi");
let sayHey = createGreeter("Hey");
console.log(sayHello("Alice")); // "Hello, Alice!"
console.log(sayHi("Bob")); // "Hi, Bob!"
console.log(sayHey("Charlie")); // "Hey, Charlie!"
// Returning functions with closures
function createCounter(start = 0) {
let count = start;
return function() {
return ++count;
};
}
let counter = createCounter(10);
console.log(counter()); // 11
console.log(counter()); // 12
console.log(counter()); // 13Callback Functions
A callback is a function passed as an argument to another function, which then executes (calls back) that function at some point.
Basic Callbacks
// Simple callback example
function greet(name, callback) {
console.log(`Hello, ${name}!`);
callback();
}
greet("Alice", function() {
console.log("Greeting complete!");
});
// Output:
// Hello, Alice!
// Greeting complete!
// With arrow function
greet("Bob", () => console.log("Done!"));
// Passing data to callback
function processUser(user, onSuccess, onError) {
if (user && user.name) {
onSuccess(user.name);
} else {
onError("Invalid user");
}
}
processUser(
{ name: "Alice", age: 25 },
name => console.log(`Welcome, ${name}!`),
error => console.log(`Error: ${error}`)
);
// Output: Welcome, Alice!
processUser(
null,
name => console.log(`Welcome, ${name}!`),
error => console.log(`Error: ${error}`)
);
// Output: Error: Invalid userWhy Use Callbacks?
// Callbacks enable customization
function calculate(a, b, operation) {
return operation(a, b);
}
// Different operations with same function
console.log(calculate(10, 5, (x, y) => x + y)); // 15
console.log(calculate(10, 5, (x, y) => x - y)); // 5
console.log(calculate(10, 5, (x, y) => x * y)); // 50
console.log(calculate(10, 5, (x, y) => x / y)); // 2
// Callbacks enable reusable patterns
function forEach(array, callback) {
for (let i = 0; i < array.length; i++) {
callback(array[i], i, array);
}
}
let fruits = ["apple", "banana", "orange"];
forEach(fruits, (fruit, index) => {
console.log(`${index}: ${fruit}`);
});
// Output:
// 0: apple
// 1: banana
// 2: orangeBuilt-in Array Higher-Order Methods
JavaScript arrays have many built-in higher-order methods that accept callbacks.
forEach() - Execute for Each Element
let numbers = [1, 2, 3, 4, 5];
// forEach executes callback for each element
numbers.forEach(function(num) {
console.log(num * 2);
});
// Output: 2, 4, 6, 8, 10
// With arrow function
numbers.forEach(num => console.log(num ** 2));
// Output: 1, 4, 9, 16, 25
// With index and array parameters
numbers.forEach((num, index, arr) => {
console.log(`Index ${index}: ${num} (Array length: ${arr.length})`);
});
// forEach returns undefined (used for side effects)
let result = numbers.forEach(num => num * 2);
console.log(result); // undefinedmap() - Transform Each Element
let numbers = [1, 2, 3, 4, 5];
// map creates new array with transformed elements
let doubled = numbers.map(function(num) {
return num * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (original unchanged)
// With arrow function
let squared = numbers.map(num => num ** 2);
console.log(squared); // [1, 4, 9, 16, 25]
// Transform objects
let users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 35 }
];
let names = users.map(user => user.name);
console.log(names); // ["Alice", "Bob", "Charlie"]
let userSummaries = users.map(user => ({
name: user.name,
isAdult: user.age >= 18
}));
console.log(userSummaries);
// [
// { name: "Alice", isAdult: true },
// { name: "Bob", isAdult: true },
// { name: "Charlie", isAdult: true }
// ]filter() - Keep Elements That Match
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// filter creates new array with elements that pass test
let evens = numbers.filter(function(num) {
return num % 2 === 0;
});
console.log(evens); // [2, 4, 6, 8, 10]
// With arrow function
let greaterThanFive = numbers.filter(num => num > 5);
console.log(greaterThanFive); // [6, 7, 8, 9, 10]
// Filter objects
let 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 }
];
let activeAdults = users.filter(user => {
return user.active && user.age >= 18;
});
console.log(activeAdults);
// [
// { name: "Alice", age: 25, active: true },
// { name: "Diana", age: 22, active: true }
// ]
// Combine conditions
let products = [
{ name: "Laptop", price: 1000, inStock: true },
{ name: "Mouse", price: 25, inStock: true },
{ name: "Monitor", price: 300, inStock: false },
{ name: "Keyboard", price: 75, inStock: true }
];
let affordableInStock = products.filter(p => {
return p.inStock && p.price < 500;
});
console.log(affordableInStock);
// [{ name: "Mouse", ... }, { name: "Keyboard", ... }]reduce() - Reduce to Single Value
let numbers = [1, 2, 3, 4, 5];
// reduce accumulates values into single result
let sum = numbers.reduce(function(accumulator, current) {
return accumulator + current;
}, 0); // 0 is initial value
console.log(sum); // 15
// How reduce works:
// Step 1: accumulator = 0, current = 1, return 0 + 1 = 1
// Step 2: accumulator = 1, current = 2, return 1 + 2 = 3
// Step 3: accumulator = 3, current = 3, return 3 + 3 = 6
// Step 4: accumulator = 6, current = 4, return 6 + 4 = 10
// Step 5: accumulator = 10, current = 5, return 10 + 5 = 15
// With arrow function
let product = numbers.reduce((acc, num) => acc * num, 1);
console.log(product); // 120
// Find maximum
let max = numbers.reduce((max, num) => num > max ? num : max);
console.log(max); // 5
// Count occurrences
let fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
let count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count);
// { apple: 3, banana: 2, orange: 1 }
// Sum prices in cart
let cart = [
{ item: "Laptop", price: 1000, quantity: 1 },
{ item: "Mouse", price: 25, quantity: 2 },
{ item: "Keyboard", price: 75, quantity: 1 }
];
let total = cart.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
console.log(total); // 1125find() and findIndex()
let numbers = [5, 12, 8, 130, 44];
// find returns first element that matches
let found = numbers.find(num => num > 10);
console.log(found); // 12
// findIndex returns index of first match
let index = numbers.findIndex(num => num > 10);
console.log(index); // 1
// With objects
let users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
let user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: "Bob" }
let userIndex = users.findIndex(u => u.name === "Charlie");
console.log(userIndex); // 2
// Returns undefined / -1 if not found
let notFound = numbers.find(num => num > 1000);
console.log(notFound); // undefined
let notFoundIndex = numbers.findIndex(num => num > 1000);
console.log(notFoundIndex); // -1some() and every()
let numbers = [1, 2, 3, 4, 5];
// some: true if ANY element matches
let hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true
let hasLarge = numbers.some(num => num > 10);
console.log(hasLarge); // false
// every: true if ALL elements match
let allPositive = numbers.every(num => num > 0);
console.log(allPositive); // true
let allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // false
// Validation example
let users = [
{ name: "Alice", age: 25, email: "alice@test.com" },
{ name: "Bob", age: 30, email: "bob@test.com" },
{ name: "Charlie", age: 22, email: "charlie@test.com" }
];
let allAdults = users.every(user => user.age >= 18);
console.log(allAdults); // true
let allHaveEmail = users.every(user => user.email && user.email.includes("@"));
console.log(allHaveEmail); // true
let anyUnder21 = users.some(user => user.age < 21);
console.log(anyUnder21); // falsesort() with Comparator
let numbers = [5, 2, 8, 1, 9];
// sort with comparator function
numbers.sort((a, b) => a - b); // Ascending
console.log(numbers); // [1, 2, 5, 8, 9]
numbers.sort((a, b) => b - a); // Descending
console.log(numbers); // [9, 8, 5, 2, 1]
// Sort objects
let users = [
{ name: "Charlie", age: 30 },
{ name: "Alice", age: 25 },
{ name: "Bob", age: 35 }
];
// Sort by age
users.sort((a, b) => a.age - b.age);
console.log(users);
// [
// { name: "Alice", age: 25 },
// { name: "Charlie", age: 30 },
// { name: "Bob", age: 35 }
// ]
// Sort by name (alphabetically)
users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users);
// [
// { name: "Alice", age: 25 },
// { name: "Bob", age: 35 },
// { name: "Charlie", age: 30 }
// ]Chaining Array Methods
You can chain multiple array methods together for powerful data transformations.
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Chain: filter → map → reduce
let result = numbers
.filter(num => num % 2 === 0) // [2, 4, 6, 8, 10]
.map(num => num ** 2) // [4, 16, 36, 64, 100]
.reduce((sum, num) => sum + num); // 220
console.log(result); // 220
// Real-world example
let products = [
{ name: "Laptop", price: 1000, category: "Electronics", inStock: true },
{ name: "Mouse", price: 25, category: "Electronics", inStock: true },
{ name: "Shirt", price: 30, category: "Clothing", inStock: false },
{ name: "Phone", price: 800, category: "Electronics", inStock: true },
{ name: "Pants", price: 50, category: "Clothing", inStock: true }
];
// Get total price of in-stock electronics
let electronicsTotal = products
.filter(p => p.category === "Electronics")
.filter(p => p.inStock)
.map(p => p.price)
.reduce((sum, price) => sum + price, 0);
console.log(electronicsTotal); // 1825
// Get names of available products, sorted
let availableProducts = products
.filter(p => p.inStock)
.map(p => p.name)
.sort();
console.log(availableProducts);
// ["Laptop", "Mouse", "Pants", "Phone"]
// Complex transformation
let users = [
{ name: "Alice", age: 25, scores: [85, 92, 78] },
{ name: "Bob", age: 17, scores: [75, 88, 95] },
{ name: "Charlie", age: 30, scores: [90, 85, 88] }
];
let adultAverages = users
.filter(user => user.age >= 18)
.map(user => ({
name: user.name,
average: user.scores.reduce((sum, s) => sum + s, 0) / user.scores.length
}))
.sort((a, b) => b.average - a.average);
console.log(adultAverages);
// [
// { name: "Alice", average: 85 },
// { name: "Charlie", average: 87.67 }
// ]Creating Your Own Higher-Order Functions
Custom Array Processor
// Create a custom map function
function myMap(array, callback) {
let result = [];
for (let i = 0; i < array.length; i++) {
result.push(callback(array[i], i, array));
}
return result;
}
let numbers = [1, 2, 3, 4, 5];
let doubled = myMap(numbers, num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Create a custom filter function
function myFilter(array, callback) {
let result = [];
for (let i = 0; i < array.length; i++) {
if (callback(array[i], i, array)) {
result.push(array[i]);
}
}
return result;
}
let evens = myFilter(numbers, num => num % 2 === 0);
console.log(evens); // [2, 4]Function Composition
// Compose multiple functions
function compose(...functions) {
return function(value) {
return functions.reduceRight((acc, fn) => fn(acc), value);
};
}
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x ** 2;
const transform = compose(square, double, addOne);
console.log(transform(5)); // ((5 + 1) * 2) ** 2 = 144
// Pipe (left to right)
function pipe(...functions) {
return function(value) {
return functions.reduce((acc, fn) => fn(acc), value);
};
}
const transform2 = pipe(addOne, double, square);
console.log(transform2(5)); // ((5 + 1) * 2) ** 2 = 144Memoization
// Cache function results
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log("Returning cached result");
return cache[key];
}
console.log("Computing result");
const result = fn(...args);
cache[key] = result;
return result;
};
}
function slowSquare(n) {
// Simulate slow operation
for (let i = 0; i < 1000000000; i++) {}
return n * n;
}
const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // Computing result, then 25
console.log(fastSquare(5)); // Returning cached result, then 25
console.log(fastSquare(10)); // Computing result, then 100
console.log(fastSquare(5)); // Returning cached result, then 25Practical Examples
Example 1: Data Processing Pipeline
// Process sales data
let sales = [
{ product: "Laptop", price: 1000, quantity: 2, category: "Electronics" },
{ product: "Mouse", price: 25, quantity: 5, category: "Electronics" },
{ product: "Shirt", price: 30, quantity: 10, category: "Clothing" },
{ product: "Phone", price: 800, quantity: 3, category: "Electronics" },
{ product: "Pants", price: 50, quantity: 8, category: "Clothing" }
];
// Get total revenue by category
let revenueByCategory = sales
.map(sale => ({
category: sale.category,
revenue: sale.price * sale.quantity
}))
.reduce((acc, item) => {
acc[item.category] = (acc[item.category] || 0) + item.revenue;
return acc;
}, {});
console.log(revenueByCategory);
// { Electronics: 4525, Clothing: 700 }
// Get top 3 products by revenue
let topProducts = sales
.map(sale => ({
product: sale.product,
revenue: sale.price * sale.quantity
}))
.sort((a, b) => b.revenue - a.revenue)
.slice(0, 3)
.map(item => item.product);
console.log(topProducts);
// ["Phone", "Laptop", "Pants"]Example 2: User Analytics
let users = [
{ name: "Alice", age: 25, purchases: [100, 50, 75] },
{ name: "Bob", age: 30, purchases: [200, 150] },
{ name: "Charlie", age: 22, purchases: [50, 25, 30, 45] },
{ name: "Diana", age: 35, purchases: [300, 250, 100] }
];
// Calculate user statistics
let userStats = users.map(user => {
let totalSpent = user.purchases.reduce((sum, p) => sum + p, 0);
let avgPurchase = totalSpent / user.purchases.length;
return {
name: user.name,
age: user.age,
totalPurchases: user.purchases.length,
totalSpent: totalSpent,
avgPurchase: avgPurchase.toFixed(2)
};
});
console.log(userStats);
// Find high-value customers (avg purchase > 100)
let highValueCustomers = userStats
.filter(user => parseFloat(user.avgPurchase) > 100)
.map(user => user.name);
console.log("High-value customers:", highValueCustomers);
// ["Bob", "Diana"]
// Get total revenue from users over 25
let revenueOver25 = users
.filter(user => user.age > 25)
.flatMap(user => user.purchases)
.reduce((sum, purchase) => sum + purchase, 0);
console.log("Revenue from users over 25:", revenueOver25);
// 1000Higher-Order Functions Practice
Experiment with callbacks and functional patterns
console.log() to see your output in the console above.Key Takeaways
- Higher-order functions accept or return other functions
- Callbacks are functions passed as arguments to be executed later
- JavaScript functions are first-class citizens
forEach()executes callback for each element (returns undefined)map()transforms each element (returns new array)filter()keeps elements that pass test (returns new array)reduce()accumulates to single valuefind(),findIndex(),some(),every()for searching- Chain array methods for powerful data transformations
- Higher-order functions enable functional programming patterns
What's Next?
Congratulations! You've completed the Functions section and now understand higher-order functions and callbacks—essential concepts for modern JavaScript development. These patterns are used everywhere, from array processing to asynchronous operations.
In the next section, we'll dive into Arrays—exploring array manipulation, methods, and advanced techniques. You'll learn how to work effectively with arrays, one of JavaScript's most important data structures!
💪 Practice Challenge:
Before moving on, try creating:
- A custom
myReduce()function that mimics the built-in reduce - A data processing pipeline that filters, transforms, and aggregates product data
- A function that composes multiple transformation functions
- A memoization function that caches expensive calculations
- A set of utility functions using higher-order function patterns