Higher-Order Functions (HOFs) are one of JavaScript's most powerful features. A function is called "higher-order" if it either takes a function as an argument or returns a function as its result. Understanding HOFs is essential for writing clean, modular, and functional-style JavaScript code.
In JavaScript, functions are first-class citizens. This means:
- Functions can be assigned to variables
- Functions can be stored in arrays and objects
- Functions can be passed as arguments to other functions
- Functions can be returned from other functions
// A regular function
function regular() {
return 42;
}
// A higher-order function (takes a function)
function hofTakes(fn) {
return fn();
}
// A higher-order function (returns a function)
function hofReturns() {
return function() {
return "Hello!";
};
}
console.log(hofTakes(regular)); // 42
console.log(hofReturns()()); // "Hello!"The most common use of HOFs is passing functions as arguments — these passed functions are called callbacks.
function greet(name, formatter) {
return formatter(name);
}
function uppercase(str) {
return str.toUpperCase();
}
function excited(str) {
return str + "!!!";
}
console.log(greet("alice", uppercase)); // "ALICE"
console.log(greet("bob", excited)); // "bob!!!"function logMessage(message, logger) {
const timestamp = new Date().toISOString();
logger(`[${timestamp}] ${message}`);
}
logMessage("Server started", console.log);
logMessage("Error occurred", console.error);Functions that return other functions create powerful patterns like function factories and partial application.
function makeMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
const quadruple = makeMultiplier(4);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(quadruple(5)); // 20Fix some arguments of a function, producing another function with fewer arguments:
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}
const sayHello = partial(greet, "Hello");
console.log(sayHello("Alice")); // "Hello, Alice!"
console.log(sayHello("Bob")); // "Hello, Bob!"JavaScript arrays come with powerful built-in HOFs.
const fruits = ["apple", "banana", "cherry"];
fruits.forEach(function(fruit, index) {
console.log(`${index}: ${fruit}`);
});
// With arrow function
fruits.forEach((fruit, index) => {
console.log(`${index}: ${fruit}`);
});Creates a new array by applying a function to every element.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
const names = ["alice", "bob", "charlie"];
const capitalized = names.map(name => name.charAt(0).toUpperCase() + name.slice(1));
console.log(capitalized); // ["Alice", "Bob", "Charlie"]Creates a new array with elements that pass a test.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 17 },
{ name: "Charlie", age: 30 }
];
const adults = users.filter(user => user.age >= 18);
console.log(adults);
// [{ name: "Alice", age: 25 }, { name: "Charlie", age: 30 }]const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15
// Finding maximum
const max = numbers.reduce((max, n) => n > max ? n : max, numbers[0]);
console.log(max); // 5
// Flatten array
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => [...acc, ...arr], []);
console.log(flat); // [1, 2, 3, 4, 5, 6]
// Count occurrences
const words = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const count = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});
console.log(count); // { apple: 3, banana: 2, cherry: 1 }const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: "Bob" }const numbers = [2, 4, 6, 8, 9];
console.log(numbers.some(n => n % 2 !== 0)); // true (at least one odd)
console.log(numbers.every(n => n % 2 === 0)); // false (not all even)const numbers = [3, 1, 4, 1, 5, 9, 2];
// Ascending
console.log(numbers.sort((a, b) => a - b)); // [1, 1, 2, 3, 4, 5, 9]
// Descending
console.log(numbers.sort((a, b) => b - a)); // [9, 5, 4, 3, 2, 1, 1]
// Sort objects by property
const users = [
{ name: "Charlie", age: 30 },
{ name: "Alice", age: 25 },
{ name: "Bob", age: 35 }
];
users.sort((a, b) => a.age - b.age);
console.log(users.map(u => u.name)); // ["Alice", "Charlie", "Bob"]HOFs become incredibly powerful when chained together:
const users = [
{ name: "alice", age: 25, active: true },
{ name: "bob", age: 17, active: false },
{ name: "charlie", age: 30, active: true },
{ name: "diana", age: 22, active: true }
];
// Get names of active adult users, capitalized
const result = users
.filter(user => user.active) // Keep only active users
.filter(user => user.age >= 18) // Keep only adults
.map(user => user.name) // Extract names
.map(name => name.charAt(0).toUpperCase() + name.slice(1)) // Capitalize
.sort(); // Sort alphabetically
console.log(result); // ["Alice", "Charlie", "Diana"]function myMap(array, callback) {
const result = [];
for (let i = 0; i < array.length; i++) {
result.push(callback(array[i], i, array));
}
return result;
}
console.log(myMap([1, 2, 3], n => n * 2)); // [2, 4, 6]function myFilter(array, callback) {
const result = [];
for (let i = 0; i < array.length; i++) {
if (callback(array[i], i, array)) {
result.push(array[i]);
}
}
return result;
}
console.log(myFilter([1, 2, 3, 4, 5], n => n > 2)); // [3, 4, 5]function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}
const initialize = once(() => {
console.log("Initializing...");
return { status: "ready" };
});
initialize(); // "Initializing..."
initialize(); // No output, returns cached result
initialize(); // No output, returns cached resultfunction debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const search = debounce((query) => {
console.log(`Searching for: ${query}`);
}, 300);
search("a");
search("ab");
search("abc"); // Only this executes after 300msfunction throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
const handleScroll = throttle(() => {
console.log("Scroll event handled");
}, 1000);
// handleScroll can only run once per secondfunction memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const slowFib = memoize(function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
console.log(slowFib(40)); // Fast due to caching!Combine functions where the output of one is the input of another:
const compose = (...fns) => (value) =>
fns.reduceRight((acc, fn) => fn(acc), value);
const pipe = (...fns) => (value) =>
fns.reduce((acc, fn) => fn(acc), value);
const add5 = x => x + 5;
const multiplyBy2 = x => x * 2;
const toString = x => String(x);
const composed = compose(toString, multiplyBy2, add5);
console.log(composed(10)); // "30" (10 + 5 = 15, 15 * 2 = 30, "30")
const piped = pipe(add5, multiplyBy2, toString);
console.log(piped(10)); // "30" (same result, different order)Transform a function with multiple arguments into a sequence of functions with single arguments:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...nextArgs) {
return curried.apply(this, [...args, ...nextArgs]);
};
};
}
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6// ❌ map should not mutate, it should transform
const nums = [1, 2, 3];
nums.map(n => {
nums.push(n * 2); // Don't do this!
});
// ✅ Return the transformed value
const doubled = nums.map(n => n * 2);// ❌ Implicit return only works without braces
const doubled = [1, 2, 3].map(n => {
n * 2; // No return!
});
// [undefined, undefined, undefined]
// ✅ Either remove braces or add return
const doubled = [1, 2, 3].map(n => n * 2);
const doubled2 = [1, 2, 3].map(n => { return n * 2; });// ❌ forEach doesn't return anything
const result = [1, 2, 3].forEach(n => n * 2);
console.log(result); // undefined
// ✅ Use map for transformations
const doubled = [1, 2, 3].map(n => n * 2);// ❌ filter().forEach() is fine, but avoid mutating during chain
const users = [...];
users
.filter(u => u.active)
.forEach(u => delete u.password); // Mutating during chainImplement a flatMap function that maps and flattens in one step.
function flatMap(array, callback) {
// Your code
}
console.log(flatMap([1, 2, 3], n => [n, n * 2]));
// [1, 2, 2, 4, 3, 6]Implement a groupBy function using reduce.
const users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "user" },
{ name: "Charlie", role: "admin" }
];
// Result should be:
// {
// admin: [{ name: "Alice", role: "admin" }, { name: "Charlie", role: "admin" }],
// user: [{ name: "Bob", role: "user" }]
// }Create a function that pipes data through multiple transformations.
const pipeline = [
x => x + 1,
x => x * 2,
x => x - 3
];
// Starting with 5: (5 + 1) * 2 - 3 = 9Combine two arrays using a function.
function zipWith(arr1, arr2, fn) {
// Your code
}
console.log(zipWith([1, 2, 3], [4, 5, 6], (a, b) => a + b));
// [5, 7, 9]Create a higher-order function that adds retry logic to any async function.
function withRetry(fn, maxAttempts) {
// Return a function that retries fn up to maxAttempts times
}- Higher-Order Functions take functions as arguments or return functions
- JavaScript treats functions as first-class citizens
- Built-in array HOFs:
map,filter,reduce,find,some,every,sort - Chaining HOFs creates readable data transformation pipelines
- Custom HOFs like
debounce,throttle,memoize, andoncesolve real problems - Composition and currying are powerful functional programming patterns
- Always return values in
map; useforEachonly for side effects
Build on HOFs by learning:
- Closures — how returned functions remember their scope
- Callbacks — asynchronous patterns with HOFs
- Array Methods Deep Dive — mastering every array HOF
Happy coding! 🚀