Yeison Daza
4 min read

Higher-Order Functions in JavaScript

I’m especially excited about this topic because it took me a while to understand the concept, since I came from other programming languages where it wasn’t common. So if I can help you understand it, that would be awesome.

Higher-order functions are a simple but very powerful concept found in all functional programming languages. It’s basically about treating functions as first-class citizens, which means that functions are objects (some authors classify JS as an OO language).

If our functions are objects, that gives us the ability to:

  • Store them in variables.
  • Pass them as arguments.
  • Return functions from functions.

Sounds interesting, right? Let’s see how to handle each case.

Storing them in variables

Let’s start with a function.

function porTres(numero) {
  return numero * 3;
}

console.log(porTres(10)); //30

Nothing new here — you can do this in any programming language (create a function, receive a parameter, and return a value). But not every language lets you do this:

let porTres = function(numero) {
  return numero * 3;
}

const multiplicaTres = porTres;

console.log(multiplicaTres(10)); //30

Storing a function in a variable.

Notice how we assign the function porTres to multiplicaTres without using parentheses. That’s because:

  • If you reference a function without parentheses, you’re passing the function object itself.
  • If you reference a function with parentheses, you’re passing the result of its execution.

Here you can find the code from these examples.

Passing them as arguments

This is probably the most commonly used feature by anyone writing JavaScript code, since it lets us create callbacks.

Callbacks are basically functions that execute once one or more other functions have finished running. This lets us write asynchronous code, since JavaScript is a Single Threaded language and can only handle one process at a time.

Writing asynchronous code lets us deal with data or processes that might take time to resolve without blocking the system while they’re being resolved. To understand how this works, you need to understand the event loop.

Let’s see how to pass functions as arguments with this example.

/*
Boton dentro de HTML
<button id="button">Touch me!</button>
*/
const button = document.getElementById('button');

button.addEventListener('click', function() {
  alert('me tocaste!');
})

When you pass a function as an argument, it will execute as soon as the event occurs, and the system won’t block while waiting for it to happen.

In the example above, we passed an anonymous function. But we could also pass an external function as a callback, like this:

button.addEventListener('click', clickAlert)

function clickAlert() {
  alert('me tocaste!');
}

This helps us avoid falling into callback hell and lets us create more generic functions. For example:

<button id="enviar">Enviar</button>
<button id="borrar">Borrar</button>

const enviar = document.getElementById('enviar');
const borrar = document.getElementById('borrar');

enviar.addEventListener('click', clickAlert);
borrar.addEventListener('click', clickAlert);

function clickAlert() {
  alert(`hiciste click en el boton ${this.id}`);
}

How does This work in JavaScript?

We’re handling the event of two buttons with a single function, making our code reusable and cleaner. Plus, it opens the door to creating pure functions.

Here you can find the code from these examples.

Returning functions

Finally, higher-order functions give us the ability to return functions. This is a very powerful feature because we can use functions as templates for other functions, and by doing so, we can leverage composition to make our code more maintainable and scalable.

To understand it better, let’s look at some code.

function masGrandeQue(n) {
  return (m) => m > n;
}

const masGrandeQue10 = masGrandeQue(10);
const masGrandeQue20 = masGrandeQue(20);

console.log(masGrandeQue10(12)); //true
console.log(masGrandeQue20(12)); //false

We created a function that returns another function, which checks which value is greater. Then we “created” two functions using our masGrandeQue definition as a template for another function. To understand a bit more about how this works, you’ll want to learn about lambdas and closures.

Let’s create another, more practical example. How about a function that replaces a word in any text?

let reemplace = function(texto) {
  return texto.replace(/mal/ig,"increible");
};

console.log(reemplace("JavaScript es un mal lenguaje"));
//"JavaScript es un increíble lenguaje"

Easy, right? What if we make it more generic so it can replace any word in any text?

let reemplace = function(original, reemplazo) {
  return (texto) => texto.replace(original,reemplazo);
};

const reemplacePorPython = reemplace(/javascript/ig, 'python');
const reemplacePorGo = reemplace(/javascript/ig, 'GO');

console.log(reemplacePorPython("JavaScript es un buen lenguaje"));
console.log(reemplacePorGo("JavaScript es un buen lenguaje"));

The reemplace function receives an original text and the replacement text, and returns a function that takes a text and replaces those values.

Then we create two functions, using our reemplace function as a template. Pretty cool!

This allows us to make our code more versatile and extensible.

Of course, to fully understand how this works you’ll need to learn a few other concepts. But with what we’ve covered here, you can already do a lot of things, and I hope you’ll share what you build.

We’ll keep talking about this in upcoming posts.

Here you can find the code from this example.