TL;DR:
- Higher-Order Functions (HOFs) are functions that receive or return other functions.
- They’re key to functional programming, widely used in React,
map()
,filter()
, and more.- They help abstract repetitive logic, making your code cleaner, testable, and elegant.
- Classic examples:
setTimeout
,Array.map()
,Array.filter()
, function factories.
Introduction
Higher-Order Functions (HOFs), or First-Class Functions, became a hot topic in JavaScript over the last few years — largely thanks to functional programming principles and React.
But despite the intimidating name, HOFs are actually pretty simple. They can be defined as:
- Functions that takes one (or more) functions as parameters, or
- Functions that return another function
But what’s the point of passing a function as a parameter? Going deeper: why even pass parameters to a function at all? What’s the use?
Parameters in Functions
Function parameters play a crucial role. Functions that receive parameters usually either perform some action with that value or transform it.
For example, take a binary search function, which receives an array of elements and a target value to search for:
function binarySearch(array, value) {
let left = 0;
let right = array.length - 1;
while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
if (array[mid] === value) {
return mid;
} else if (array[mid] < value) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
const numbers = [1, 3, 5, 7, 9, 11, 13, 15];
console.log(binarySearch(numbers, 7)); // 3
console.log(binarySearch(numbers, 10)); // -1
The function needs the target value to know what to look for, and the array to know where to look and what index to return.
But sometimes, passing primitive values like numbers, arrays, or strings isn’t enough. In more complex cases, the logic itself needs to be passed as a function.
Functions as Parameters
A perfect example of a function that needs another function is setTimeout()
. This function executes an action after a delay. But it needs to know what to execute — which is why we pass it a function.
Think of it like calling your ISP and following the steps to reset your modem:
- Turn it off
- Wait 5 seconds
- Turn it back on
const modem = {
on: true,
toggle() {
this.on = !this.on;
console.log(this.on);
},
};
// Turn off the modem
modem.toggle();
// Turn it back on after 5000ms (5s)
setTimeout(modem.toggle, 5000);
Passing a number alone wouldn’t be enough for setTimeout()
— it needs a function to execute after the time delay.
Other common examples include Array.map()
and Array.filter()
.
If you, like me, learned programming in a language with no functional programming (or sometimes not even OOP — shoutout to the C devs, I do not miss that), you probably ignored these functions and wrote loops instead:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let lessThan4 = [];
let j = 0;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] < 4) {
lessThan4[j] = numbers[i];
j++;
}
}
But when you need to chain multiple steps, this quickly becomes messy and unreadable. Suppose we want to filter even numbers less than 4, then multiply them by 2:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let lessThan4 = [];
let j = 0;
// Filter < 4
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] < 4) {
lessThan4[j] = numbers[i];
j++;
}
}
// Filter evens
let evens = [];
j = 0;
for (let i = 0; i < lessThan4.length; i++) {
if (lessThan4[i] % 2 === 0) {
evens[j] = lessThan4[i];
j++;
}
}
// Multiply by 2
let result = [];
for (let i = 0; i < evens.length; i++) {
result[i] = evens[i] * 2;
}
console.log(result); // [4]
That’s a lot of logic for a simple transformation. With HOFs, we abstract all that into readable, functional chains using filter()
and map()
:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result = numbers
.filter(n => n < 4)
.filter(n => n % 2 === 0)
.map(n => n * 2);
console.log(result); // [4]
And if the logic gets more complex or will be reused, you can define it in named functions and pass those:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result = numbers
.filter(lessThan4)
.filter(isEven)
.map(double);
function lessThan4(n) {
return n < 4;
}
function isEven(n) {
return n % 2 === 0;
}
function double(n) {
return n * 2;
}
console.log(result); // [4]
This makes your code more readable, reusable, and testable. (Seriously — write tests. Your future self will thank you.)
Returning Functions
As I said earlier, functions that return other functions are also considered HOFs.
Let’s say you have multiple buttons, and each applies a different theme to the page (light, dark, high-contrast). But besides applying styles, you also want to:
- Save the preference to
localStorage
- Log which theme was selected
<button onclick="applyLightTheme()">Light</button>
<button onclick="applyDarkTheme()">Dark</button>
<button onclick="applyHighContrastTheme()">High Contrast</button>
<script>
function applyLightTheme() {
document.body.className = 'light';
localStorage.setItem('theme', 'light');
console.log('Theme: light');
}
function applyDarkTheme() {
document.body.className = 'dark';
localStorage.setItem('theme', 'dark');
console.log('Theme: dark');
}
function applyHighContrastTheme() {
document.body.className = 'contrast';
localStorage.setItem('theme', 'contrast');
console.log('Theme: contrast');
}
</script>
But the logic is repeating — and we don’t like repetition. So we use a Function Factory (not to be confused with Factory Functions 😄).
A function factory is a HOF — it builds and returns a function:
<button onclick="applyLightTheme()">Light</button>
<button onclick="applyDarkTheme()">Dark</button>
<button onclick="applyHighContrastTheme()">High Contrast</button>
<script>
function makeThemeApplier(themeName) {
return function () {
document.body.className = themeName;
localStorage.setItem('theme', themeName);
console.log(`Theme: ${themeName}`);
};
}
const applyLightTheme = makeThemeApplier('light');
const applyDarkTheme = makeThemeApplier('dark');
const applyHighContrastTheme = makeThemeApplier('contrast');
</script>
The makeThemeApplier()
function is a Higher-Order Function because it receives a value and returns a function that remembers that value (thanks to closures).
That’s the core power of HOFs:
“Give me a parameter, and I’ll give you back a function that uses it.”
And if you’re wondering what a closure is — don’t worry. That’s exactly what we’ll explore in the next episodes.
Recap
A Higher-Order Function:
- Accepts a function as an argument, or
- Returns a new function
They're everywhere: setTimeout(), map(), filter(), reduce()
They help you:
- Abstract logic
- Write cleaner code
- Compose behavior
Enable powerful patterns like:
- Function factories
- Closures
- Declarative pipelines
Perfeito! Aqui está seu post formatado para publicação no dev.to, com título, seções, marcação em markdown e preservando o estilo da série “Do You Know How It Works?”:
Wrapping Up
HOFs are a gateway to functional programming in JavaScript.
They help you write expressive, declarative, and clean code.
If you’ve ever wondered why map()
and filter()
feel so magical — it’s because they are.
But we’ve only scratched the surface.
Next up: JavaScript Scopes —
What exactly can your function “see”?
Let’s uncover the rules behind variable visibility, hoisting, and why sometimes things just don’t exist where you expect them to..
💬 Have a cool use case for HOFs?
Drop it in the comments — I’d love to learn how you’re using them in your projects!
📬 If you're enjoying this series, consider saving it or sharing it with a fellow JS dev. Let’s grow together!
👉 Follow me @matheusjulidori for the next episodes of Do You Know How It Works?
🎥 Subscribe to my YouTube channel to be the first ones to watch my new project as soon as it is released!
Top comments (0)