DEV Community

Cover image for Higher-order functions & Why you should use them
Amin
Amin

Posted on

Higher-order functions & Why you should use them

This article assumes that you have a good understanding of the JavaScript syntax so that it will be easy for you to grasp the inner concepts behind this article.

A good understanding of some of the most known Array.prototype methods is appreciated, although I'll try my best to add some details for a complete understanding of those.

This article is very important if you want to step up your game! Do not be ashamed to read this more than once. In fact, I made this article primarily for myself because I need a reference article that I can go back from time to time.

Who should read this article?

Pretty much everyone. A higher-order function is a concept that is used in JavaScript and that you already have used without knowing it.

Whether you are building a Web application, writing your own CLI, scrapping the Web to build an API, or conducting a data-analysis operation and want to build bigger and more complex applications, this article is for you.

What is a higher-order function?

The concept behind a higher-order function is not directly linked to the JavaScript programming language or any language at all.

A higher-order function is simply a function that either takes one (or more) function as its argument or returns a function.

So for instance, Array.prototype.filter, which helps you filter an array based on a predicate function, is a higher-order function. Why? Because it takes one function as its argument.

const fruits = ["Banana", "Apple", "Pear"];

const longFruits = fruits.filter((currentFruit) => {
  return currentFruit.length > 4;
});

for (const fruit of longFruits) {
  console.log(fruit);
  // Banana
  // Apple
}
Enter fullscreen mode Exit fullscreen mode

In the other hand, Array.prototype.slice is not a higher-order function, even though it also belongs to the Array.prototype prototype just like Array.prototype.filter. Why? Because Array.prototype.slice does not accept a function as its argument, nor it returns a function.

const fruits = ["Banana", "Apple", "Pear"];

const firstTwoFruits = fruits.slice(0, 2);

for (const fruit of firstTwoFruits) {
  console.log(fruit);
  // Banana
  // Apple
}
Enter fullscreen mode Exit fullscreen mode

Why are higher-order functions useful?

Higher-order functions are useful when you want to make your code terse. But they are especially useful to make your code more flexible and maintainable while still remaining relevant and useful.

Let's try to code our own filter function that will not be a higher-order function (for now) to see what problem higher-order functions solve.

I'll write a simple filter function that will take a number (the length of the elements that should stay) and an array (a list of strings) just like our previous example.

const filter = (length, items) => {
  const output = [];

  for (const item of items) {
    if (item.length > length) {
      output.push(item);
    }
  }

  return output;
};

const fruits = ["Banana", "Apple", "Pear"];

const longFruits = filter(4, fruits);

for (const fruit of longFruits) {
  console.log(fruit);
  // Banana
  // Apple
}
Enter fullscreen mode Exit fullscreen mode

So we got the same output, and we achieved the same goal as earlier. So why bother with higher-order functions if I can make a simpler code like that work?

Well, you probably guessed: our code is not very flexible. If I have a list of numbers that I want to filter, I have to create another function (and probably rename my current filter function to have a more maintainable set of helper functions).

const stringsLengthGreaterThan = (length, items) => {
  const output = [];

  for (const item of items) {
    if (item.length > length) {
      output.push(item);
    }
  }

  return output;
};

const numbersGreaterThan = (value, numbers) => {
  const output = [];

  for (const number of numbers) {
    if (number > value) {
      output.push(number);
    }
  }

  return output;
};

const fruits = ["Banana", "Apple", "Pear"];
const marks = [15, 12, 6, 19, 7, 9];

const longFruits = stringsLengthGreaterThan(4, fruits);
const highMarks = numbersGreaterThan(10, marks);

for (const fruit of longFruits) {
  console.log(fruit);
  // Banana
  // Apple
}

for (const mark of highMarks) {
  console.log(mark);
  // 15
  // 12
  // 19
}
Enter fullscreen mode Exit fullscreen mode

That works out well, but now you have to filter out only users that have not activated their account yet to send them a reminder email for their account to check if everything is okay.

Yes, you will have to write another filter function and that is so much time spent on writing those functions because that also means that you will have to write as many tests as there are functions. So not very maintainable nor flexible, isn't it?

So the point is that these two functions are not higher-order functions because they do not take a function as an argument, and they do not return a function.

Let's see now how a higher-order function can decrease the amount of work we have to do, using the same data as before.

const fruits = ["Banana", "Apple", "Pear"];
const marks = [15, 12, 6, 19, 7, 9];

const longFruits = fruits.filter(fruit => {
  return fruit.length > 4;
});

const highMarks = marks.filter(mark => {
  return mark > 10
});

for (const fruit of longFruits) {
  console.log(fruit);
  // Banana
  // Apple
}

for (const mark of highMarks) {
  console.log(mark);
  // 15
  // 12
  // 19
}
Enter fullscreen mode Exit fullscreen mode

We achieve the exact same result, but we used the Array.prototype.filter method, which is, again, a higher-order function because it takes a function as its argument.

So you know that you want to filter an array based on a condition.

Filtering an array is pretty simple and can be encoded pretty easily. Whether the array is of length 1 or 100000000 you know that the code will remain the same.

But you cannot afford to encode all possible cases for filtering an array. Maybe you want to filter an array of strings by their length. Or maybe you want to filter an array of numbers based on their value. Or maybe you want to filter an array of promises based on their state. There are an infinite amount of possibilities and your function would be a monster function if you try to encode all possible cases.

Hence the usefulness of writing a higher-order function because you will let the user encode their own business logic to filter out an array while still filtering the array by yourself based on their logic.

Hence why a higher-order function is pretty darn useful and is a very important skill to master if you want to enhance your programming game, not only just in JavaScript even though I used JavaScript to show you how it was done.

How can I create my own higher-order function?

Well, again, a higher-order function is a function that either takes a function as its argument or returns a function. We saw the first case, now let's see another interesting case where you might want to create your own higher-order function.

Say you want to filter out an array of marks from your students. You are given their marks, and you have to display the grades above ten and below ten. This will help the school know if the course is too easy or too hard. You are convinced that JavaScript is the right tool to achieve this goal so naturally, you write a JavaScript module for that.

const marks = [15, 12, 6, 19, 7, 9];

const marksAboveTen = marks.filter(mark => {
  return mark > 10;
});

const marksBelowTen = marks.filter(mark => {
  return mark < 10;
});

console.log(marksAboveTen); // [15, 12, 19]
console.log(marksBelowTen); // [6, 7, 9]
Enter fullscreen mode Exit fullscreen mode

The school now wants you to display the marks above fifteen and below five, and also marks that are equal to ten to have even more data to analyze.

const marks = [15, 12, 6, 19, 7, 9];

const marksAboveTen = marks.filter(mark => {
  return mark > 10;
});

const marksBelowTen = marks.filter(mark => {
  return mark < 10;
});

const marksAboveFifteen = marks.filter(mark => {
  return mark > 15;
});

const marksBelowFive = marks.filter(mark => {
  return mark < 5;
});

const marksEqualToTen = marks.filter(mark => {
  return mark === 10;
});

console.log(marksAboveTen);     // [15, 12, 19]
console.log(marksBelowTen);     // [6, 7, 9]
console.log(marksAboveFifteen); // [19]
console.log(marksBelowFive);    // []
console.log(marksEqualToTen);   // []
Enter fullscreen mode Exit fullscreen mode

So that works fine, but you start to see a pattern in all those predicates: you are always comparing a number to another. It is time for us to use some higher-order function goodies to make our lives easier.

There are three cases here: we either compare marks that are higher, lower, or equal. Let's encode the first comparison that is a mark that is above another one.

const marks = [15, 12, 6, 19, 7, 9];

const above = value => {
  return item => {
    return item > value;
  };
};

const marksAboveTen = marks.filter(above(10));

const marksBelowTen = marks.filter(mark => {
  return mark < 10;
});

const marksAboveFifteen = marks.filter(above(15));

const marksBelowFive = marks.filter(mark => {
  return mark < 5;
});

const marksEqualToTen = marks.filter(mark => {
  return mark === 10;
});

console.log(marksAboveTen);     // [15, 12, 19]
console.log(marksBelowTen);     // [6, 7, 9]
console.log(marksAboveFifteen); // [19]
console.log(marksBelowFive);    // []
console.log(marksEqualToTen);   // []
Enter fullscreen mode Exit fullscreen mode

So the result is the same, but we did write a higher-order function. Why? Because our above function is a function that accepts a number (so not a function), but returns a function. That's it. We have our higher-order function.

But why is it useful? Well, if you look at how we use this function, you see that we don't have to write our comparison ourselves now. We can just say okay, I want you to filter those marks and get only those that are higher than 10 or 15. And it reads just like plain English. marks.filter(above(15));

But why does it have to return a function? Well, remember what we said about Array.prototype.filter being a higher-order function? That is why.

Since Array.prototype.filter accepts a function and nothing else, we have, one way or the other, to return a function. This means that our above(15) must return a function. But it also has to accept a value argument, hence why we wrote a function that returns a function.

And we could apply the same logic for the below and equals higher-order functions too.

const marks = [15, 12, 6, 19, 7, 9];

const above = value => {
  return item => {
    return item > value;
  };
};

const below = value => {
  return item => {
    return item < value;
  };
};

const equals = value => {
  return item => {
    return item === value;
  };
};

const marksAboveTen = marks.filter(above(10));

const marksBelowTen = marks.filter(below(10));

const marksAboveFifteen = marks.filter(above(15));

const marksBelowFive = marks.filter(below(5));

const marksEqualToTen = marks.filter(equals(10));

console.log(marksAboveTen);     // [15, 12, 19]
console.log(marksBelowTen);     // [6, 7, 9]
console.log(marksAboveFifteen); // [19]
console.log(marksBelowFive);    // []
console.log(marksEqualToTen);   // []
Enter fullscreen mode Exit fullscreen mode

And we got ourselves the same exact result. Plus the API is neat. And we can easily add more data analysis if the school request more from us.

If we wanted to, we could also write all these in their own line to take full advantage of arrow functions in JavaScript.

const marks = [15, 12, 6, 19, 7, 9];

const above = value => item => item > value;
const below = value => item => item < value;
const equals = value => item => item === value;

const marksAboveTen = marks.filter(above(10));
const marksBelowTen = marks.filter(below(10));
const marksAboveFifteen = marks.filter(above(15));
const marksBelowFive = marks.filter(below(5));
const marksEqualToTen = marks.filter(equals(10));

console.log(marksAboveTen);     // [15, 12, 19]
console.log(marksBelowTen);     // [6, 7, 9]
console.log(marksAboveFifteen); // [19]
console.log(marksBelowFive);    // []
console.log(marksEqualToTen);   // []
Enter fullscreen mode Exit fullscreen mode

And again, we got ourselves the same exact result as before, only the code is terser but still readable and maintainable.

Conclusion

We saw what a higher function was. We also found that there were already some native methods that were using higher-order functions. We saw why we should use a higher-order function and most importantly when. And you are now able to write your own higher-order functions.

Higher-order functions are part of a greater scheme that is functional programming. And it has tons of other useful concepts that help us build better, more reliable, and more maintainable applications. So there is an entire world of new concepts that you can learn to become a better developer.

I hope that you enjoyed this article as I did and if you have any questions or anything else don't hesitate to ask in the comment section below, I would be glad to have your take on this concept as well so feel free to drop a comment!

Take care and keep learning.

Top comments (4)

Collapse
 
johnkazer profile image
John Kazer

Your final example is more-or-less currying, which would also be a useful next step. Also, rather than adding the first argument in the filter() call, you could set this up earlier and make the functions (possibly) even more re-usable:

const above10 = above(10);
const marksAboveTen = marks.filter(above10);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
aminnairi profile image
Amin

Hi John and thanks for pointing that out!

Indeed currying has made its way out (in a shy way) in this example, I tried to keep the focus on higher-order functions for beginners but you are totally right and those topics are linked together. There is still plenty room for optimizations.

I'm glad that you brought that subject at light so that readers can check that out if they are curious. Unfortunately I would require an entire website if I were to speak about functional programming because this subject is really fun and interesting and full of good ideas and concepts!

Collapse
 
arvindsridharan profile image
arvindsridharan

Amin, you have very nicely explained higher order functions. I have bookmarked your post for reference. I believe closures, recurssive functions and call backs are also Higher order functions. Please prepare some posts on this.

Collapse
 
aminnairi profile image
Amin

Hi and thanks for you comment. That means a lot.

Indeed, closures, recursive functions and callbacks can create higher order functions.

I'll try my best to make articles about these topics in a near future and I'll keep you updated when they are ready.