Higher Order Functions in JavaScript
A higher-order function is just a function that plays with other functions. That's it.
Basically it either -
Take a function as input
returns a function
Most of the time, it does both. But even doing one of those things makes it "higher-order."
Think of it like this: regular functions work with data numbers, strings, objects. Higher-order functions work with behavior. Here's the thing that makes JavaScript special, functions are first-class citizens.
In some languages, functions are these rigid, special things. In JavaScript, a function is just another piece of data you can move around. That's why higher-order functions are so natural here we're not fighting the language, we're using it the way it was designed.
Let's Look at Some Basic Examples
Passing a Function as an Argument
This is probably the easiest place to start.
function greet(name) {
return `Hey there, ${name}!`;
}
function formalGreet(name) {
return `Good evening, ${name}.`;
}
function saySomething(name, greetingFunction) {
return greetingFunction(name);
}
console.log(saySomething("Sam", greet)); // "Hey there, Sam!"
console.log(saySomething("Sam", formalGreet)); // "Good evening, Sam."
Returning a Function
function makeMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const triple = makeMultiplier(3);
const double = makeMultiplier(2);
console.log(triple(5)); // 15
console.log(double(5)); // 10
makeMultiplier is like a little factory. You tell it what factor you want, and it builds you a custom function that remembers that factor.
The Built-In HOF
JavaScript gives us three classics that pretty much every developer uses daily. Map, Filter, Reduce.
Lets start with MAP
Use map when you have an array and want to turn every item into something else. Same number of items, different values.
const prices = [10, 20, 30];
const withTax = prices.map(price => price * 1.2);
// [12, 24, 36]
Next is FILTER
Use filter when you want to remove stuff based on a condition. You might end up with fewer items than you started with.
const ages = [15, 22, 18, 30, 12];
const adults = ages.filter(age => age >= 18);
// [22, 18, 30]
REDUCE
Use reduce when you want to take a whole array and collapse it into one value. Could be a sum, an object, whatever.
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((total, num) => total + num, 0);
// 10
The key thing: don't get hung up on memorizing syntax. Focus on intent. When you look at a problem, ask yourself: "Am I transforming, selecting, or aggregating?" That tells you which tool to grab.
Closures: How Functions Remember Things
When a function is created inside another function, it keeps access to all the variables from its parent even after the parent finishes. It's like the inner function carries a backpack with all the stuff it needs.
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const myCounter = createCounter();
console.log(myCounter.increment()); // 1
console.log(myCounter.increment()); // 2
console.log(myCounter.getCount()); // 2
console.log(myCounter.decrement()); // 1
console.log(myCounter.count); // undefined (it's protected!)
Nothing from the outside can touch it directly, but the functions we returned can still use it. This is a classic pattern for creating configurable, stateful behavior while keeping your data safe.
function createLogger(prefix) {
return function(message) {
console.log(`[${prefix}] ${message}`);
};
}
const apiLogger = createLogger("API");
const dbLogger = createLogger("DATABASE");
apiLogger("Request received");
dbLogger("Connection failed");
Real-World Stuff You'll Actually Use
Logging Wrappers
Want to log how long a function takes without cluttering your business logic? here is an example
function withTiming(fn) {
return function(...args) {
const start = Date.now();
const result = fn(...args);
const end = Date.now();
console.log(`${fn.name} took ${end - start}ms`);
return result;
};
}
const slowFunction = () => {
// ... some heavy work
};
const timedSlowFunction = withTiming(slowFunction);
timedSlowFunction(); // Logs: "slowFunction took 45ms"
Retry Logic
APIs fail sometimes. Let's make a wrapper that tries again -
function withRetry(fn, maxAttempts = 3) {
return async function(...args) {
for (let i = 1; i <= maxAttempts; i++) {
try {
return await fn(...args);
} catch (error) {
if (i === maxAttempts) throw error;
console.log(`Attempt ${i} failed, retrying...`);
}
}
};
}
const fetchWithRetry = withRetry(fetchUserData, 3);
Higher order functions aren't some academic CS concept they're just functions that work with other functions. JavaScript makes this easy because functions are just values we can pass around.