Why Do You Need Higher-Order Functions?
A higher-order function is one that takes another function as an argument or returns a function.
For example, suppose you have a function x
, and we simply log "Hello World"
to the console.
function x() { console.log("Hello World"); }
Now, imagine we also have a function y
that takes x
as an argument and calls it.
function x() { console.log("Hello World"); } function y(x) { x(); }
In this case, y
, which takes another function x
as an argument, is the higher-order function.
So what is x
then? x
is called a callback function.
Practical Example
Let's say we have an array radius
which contains the radii of four circles:
const radius = [1, 2, 3, 4];
Now our task is to calculate the area of all these four circles. How do we do that?
Most of us would write a function like this. We would create a function calculateArea
that takes radius
as a parameter.
const calculateArea = function (radius) {};
Next, we'll create a new array called output
that will store the areas of all the radii:
const calculateArea = function (radius) { const output = []; };
We will iterate through all the radii and push the calculated area into the output
array. The area of a circle is Math.PI * radius * radius
.
const calculateArea = function (radius) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(Math.PI * radius[i] * radius[i]); } return output; };
Let's see if this function works properly by logging the result to the console:
const calculateArea = function (radius) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(Math.PI * radius[i] * radius[i]); } return output; }; console.log(calculateArea(radius)); // [3.141592653589793, 12.566370614359172, 28.274333882308138, 50.26548245743669]
As you can see, these are the areas of four circles.
This function works fine. Now, lI'll show you the common mistakes people make as the code grows.
What if you also had to calculate the circumference of these circles?
Suppose you also need to calculate the circumference of these circles. Many people would write a function calculateCircumference
in a similar way. Here’s how it might look:
const calculateCircumference = function (radius) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(2 * Math.PI * radius[i]); } return output; }; console.log(calculateCircumference(radius)); // [6.283185307179586, 12.566370614359172, 18.84955592153876, 25.132741228718345]
Now, if you also needed to calculate the diameter, many people would copy the calculateCircumference
function and just update the logic:
const calculateDiameter = function (radius) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(2 * radius[i]); } return output; }; console.log(calculateDiameter(radius)); // [2, 4, 6, 8]
This is how I've seen many people, in their haste, end up copying, pasting, and modifying code. However, this approach is not ideal. Can you identify the problem here?
Take a moment to think about it. If you can identify the problem, great! If not, let me explain it to you.
Writing Better, Modular Code
Now, I’ll show you what interviewers and other developers expect when writing code. What do we mean by reusable code? What is modular code? How do you write code in a functional way?
First, let’s identify the problem here.
One obvious issue is that we are repeating ourselves extensively.
There’s a principle in software engineering called DRY—Don't Repeat Yourself. In the above code, we see that 90% of the code is duplicated across the three functions.
The only part that changes is the specific logic—whether it's for calculating area, circumference, or diameter.
So, how can we optimize this? Let’s look at a better approach.
We are creating the output
array each time. We are repeating the iteration over the radius
array. We are using and returning the output
array each time.
The only thing that changes is the logic.
So first, can we just extract this logic out? Calculating the area outside? Yes, we can.
The good way to do this is to create a function area
. The only responsibility of this function is to take the radius and return the area of the circle.
const area = function (radius) { return Math.PI * radius * radius; };
Now we will try to make the calculateArea
code function generic, and we will try to put the logic into this function from outside.
This might sound complex, but it will make sense soon. Instead of just passing the radius into this function, we will also be passing some logic inside this function.
Doesn't this sound interesting?
Let's try to write the generic function. Let's name it calculate
, and not calculateArea
, calculateCircumference
, or calculateDiameter
. We will pass the specific logic from outside.
It will be doing a similar job, so let us start by just copying the function.
const calculate = function (radius) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(Math.PI * radius[i] * radius[i]); } return output; };
Now we need to pass this logic Math.PI * radius[i] * radius[i]
from outside of the function. And this logic could be anything. This logic could be calculating the area, or calculating the diameter, or calculating the circumference.
So we will pass the logic as a parameter. And use logic(radius[i])
inside output.push
.
const calculate = function (radius, logic) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(logic(radius[i])); } return output; };
This might sound complicated. Let me go through this again. Look at these two functions:
const calculateArea = function (radius) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(Math.PI * radius[i] * radius[i]); } return output; }; const calculate = function (radius, logic) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(logic(radius[i])); } return output; };
These two functions look almost similar. Instead of passing the logic directly Math.PI * radius[i] * radius[i]
, we are just passing the logic as a parameter and using it.
So now let's print it. Along with the radius, we will pass the function area
.
const area = function (radius) { return Math.PI * radius * radius; }; const calculate = function (radius, logic) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(logic(radius[i])); } return output; }; console.log(calculate(radius, area)); // [3.141592653589793, 12.566370614359172, 28.274333882308138, 50.26548245743669]
If you closely see, we are passing the area
function as the logic. This area
function will be called again and again for each element in this radius
array, and it will be pushed inside the output
.
And if you see, the output will be the same.
Benefits of Abstraction
So you might ask this question: Why did you abstract area
out? Is it really helpful?
But now you’ll see the advantage of this approach. Suppose we need to calculate the circumference. We don’t have to copy the entire function. Instead, we can just write a small function for the circumference logic:
const circumference = function (radius) { return 2 * Math.PI * radius; };
To calculate the circumference for all these radii, we simply pass this logic into our calculate
function:
const area = function (radius) { return Math.PI * radius * radius; }; const circumference = function (radius) { return 2 * Math.PI * radius; }; const calculate = function (radius, logic) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(logic(radius[i])); } return output; }; console.log(calculate(radius, area)); // [3.141592653589793, 12.566370614359172, 28.274333882308138, 50.26548245743669] console.log(calculate(radius, circumference)); // [6.283185307179586, 12.566370614359172, 18.84955592153876, 25.132741228718345]
If we need to calculate the diameter, we only need to add another function for the diameter logic:
const diameter = function (radius) { return 2 * radius; };
We can then pass this new logic into the calculate
function to get the diameters of the circles:
const area = function (radius) { return Math.PI * radius * radius; }; const circumference = function (radius) { return 2 * Math.PI * radius; }; const diameter = function (radius) { return 2 * radius; }; const calculate = function (radius, logic) { const output = []; for (let i = 0; i < radius.length; i++) { output.push(logic(radius[i])); } return output; }; console.log(calculate(radius, area)); // [3.141592653589793, 12.566370614359172, 28.274333882308138, 50.26548245743669] console.log(calculate(radius, circumference)); // [6.283185307179586, 12.566370614359172, 18.84955592153876, 25.132741228718345] console.log(calculate(radius, diameter)); // [2, 4, 6, 8]
When writing code, you should follow this approach. As an interviewer, I expect you to write your code this way.
Let me explain why this is considered good code. By abstracting the code into separate functions, we give each function a specific responsibility.
For example, the area
function has one task: calculating the area. Similarly, the circumference
and diameter
functions each have a single responsibility: calculating the circumference and diameter, respectively.
The calculate
function has its own role: it creates an output array, iterates through all elements, pushes values to the array, and returns it. There is no repetition in this approach.
This illustrates the beauty of functional programming. We break down tasks into small, reusable functions.
Learn Higher-Order Functions
Write code that is easier to understand and maintain.
You've heard about higher-order functions, but every explanation you've come across is either too technical or too vague. You're searching for something clear and practical, with examples that will help you practice and truly master the concept.
Higher-order functions are key to writing better code, essential for your projects, and expected in interviews. But getting started can be tough.
But what if you could? What if you had access to easy-to-understand content that not only explains higher-order functions but also provides hands-on practice? You’d finally feel confident using them in your projects, knowing exactly what they do and how to leverage them effectively.
It’s true, finding the right resources to learn higher-order functions can be challenging… but it doesn’t have to be.
Get my guide, where you’ll:
- Understand why higher-order functions are worth learning and how they can transform your approach to writing JavaScript.
- Gain clarity as I break down higher-order functions into simple, everyday language.
- Master three important higher-order functions:
map
,filter
, andreduce
, with plenty of examples to solidify your understanding. - Apply your knowledge with real-world examples that demonstrate how these concepts work together, so you can use them easily in your projects or write better code in your interviews.
- Get practice exercises to help reinforce what you’ve learned and boost your confidence in understanding the concepts.