Photo by Markus Spiske on Unsplash

Setting the Bar ‘Higher’

HIGHER-ORDER FUNCTIONS

Christopher Golizio
Published in
8 min readJan 15, 2021

--

When a function involves a separate function during its execution, it is known as a higher-order function. This involvement happens in one of two ways: either the original function takes another function as an argument, or it returns another function as its result. This separate function being passed to, or returned from a higher-order function is often referred to as its callback function. The callback function can be written as an anonymous function, directly inside the original function, or it can be initialized to a variable in a separate part of the code and called with its variable name.

Higher-order functions are powerful tools that afford developers the ability to break up a complicated task into smaller, simpler tasks. These small tasks can then work together to tackle the original, more complex problem. Through this technique, code becomes more legible, and bugs become less frequent. To put it plainly, higher-order functions can make developers better at writing code, in more ways than one.

There are many examples of higher-order functions, but there are three that tend to stand out from the others, in terms of both the regularity of which they are used and the various applicable ways in which they can be used. What 3 functions are they? Well, I’m glad you asked; they’re the infamous map, filter, and reduce.

All three of these functions take a callback as one of their arguments, however, reduce can optionally receive another argument, often called a ‘seed’ value. More on that later. They are all called on arrays, and while reduce can return any value, map and filter each return an array. The array returned by map and filter is a brand new array; in other words, the array upon which these higher-order functions are called remains unchanged. In the most basic of terms, these three functions iterate over the collection passed to them and run their passed-in callback function on each item in the collection, before populating a new array with the appropriate values, and returning said array.

How do they actually work?

INTRODUCTION

Map will ultimately populate a new array with the return values of the items in the original array after they are passed through the callback. Filter will run each array element through its callback function and populate a new array with any of the elements that resolve to truthy values. Reduce will, for lack of a better term, reduce the array values into a single value.

MAP

The arguments passed to the map function are generally always the same. Depending on how it’s called (whether as an array method, or a function) it takes either an array and callback function, or simply a callback function. The arguments passed into the callback function are always the same as well, though only the first of the three is required when map is implemented. These callback function arguments include the current value of the array, the index of that value, and the array itself. The iterator will begin at the first element (current value) and pass it through the callback. It will take the return value of the callback function call and place it inside a brand new array. Then the next item will become the “current value”, and it will be passed into the callback function, subsequently placing the return value in the new array. This continues until the function has passed each of the original array’s values through its callback function. Finally, the new array containing the callback function’s results will be returned.

var originalArray = [5, 10, 15, 20, 25];var addFive = function(num){
return num += 5
};
let addFiveArray = originalArray.map(addFive); console.log(addFiveArray); //=> [10, 15, 20, 25, 30]

This demonstrates using the map function on an array of numbers. A function called ‘addFive’ is created, and a variable named ‘addFiveArray’ is initialized to the value of map being called on ‘originalArray’, passing in the ‘addFive’ function. The numbers inside ‘originalArray’ are individually passed into ‘addFive’ and the return values are placed inside a brand new array. This newly created array, with the new values, is then returned. In this example, each number has 5 added to it, before it is placed in the returned array.

var nums = [1, 2, 3, 4, 5];var oddOrEven = nums.map(function(num){
return num % 2 === 0 ? 'even' : 'odd'
});
console.log(oddOrEven); //=> ['odd', 'even', 'odd', 'even', 'odd']

Here, we can see another array named ‘nums’ which is filled with numbers. A variable called ‘oddOrEven’ is initialized to the array calling map. Map has an anonymous callback function passed to it. This callback function checks if the current number has no remainder when divided by 2. If so then the number is even. If the number is even, map will place a string of ‘even’ to the returned array, in the same indexed position as the relevant number in the ‘nums’ array. Finally, an array of strings is returned.

FILTER

As is the case with the map function, filter takes an array and a callback function. Also similar to map, filter will take the same three callback function arguments (current value, index, and the array), and again, only the first of the three is required. The callback function can be thought of as a ‘test’. Array elements that resolve to a truthy value when passed to the callback function have ‘passed’ the ‘test’. Filter runs each array item through the callback function, then places any item that ‘passes’ the ‘test’ into a new array and returns the new array.

var quarterbacks = [
{name: 'Drew Brees', team: 'Saints'},
{name: 'Tom Brady', team: 'Bucs'},
{name: 'Patrick Mahomes', team: 'Chiefs'},
{name: 'Taysom Hill', team: 'Saints'},
{name: 'Josh Allen', team: 'Bills'}
];

var saints = quarterbacks.filter(function(qbObj){
return qbObj.team === 'Saints';
});

console.log(saints); //=> [ {name: 'Drew Brees', team: 'Saints'},
{name: 'Taysom Hill', team: 'Saints'} ]

In this example, we see there is an array named ‘quarterbacks’. The ‘quarterbacks’ array is populated by objects. Each object has a name and a team property. When filter is called with the ‘quarterbacks’ array, an anonymous callback function is passed as the argument of filter, as expected. This example shows one parameter being passed to the anonymous function called ‘qbObj’. This parameter will refer to each object within the ‘quarterbacks’ array. Filter will iterate over them one by one. Then a return statement tells Javascript what it should test each ‘qbObj’ in the ‘quarterbacks’ array for. In this case, Javascript will check whether or not the team property of each ‘qbObj’ is strictly equal to a string of ‘Saints’. If the team property is ‘Saints’, then the function will place the relevant ‘qbObj’ into a new array. After filter has passed all the elements of the ‘quarterbacks’ array through the callback function, it will return an array containing any ‘qbObj’ that has that particular value attached to its team property.

REDUCE

As useful as map and filter are to developers, reduce is often considered an even more powerful tool when compared to them. This is at least partially based on how versatile it is. Another side note of reduce is that most of the problems solved using map or filter can also be solved using reduce, however, this is not the case vice versa. Like the two functions described above, reduce similarly runs on an array, and takes a callback function as an argument. The parameters passed to the callback function consist of an ‘accumulator’ value, the current element of the array, the index of the current element, and lastly the array itself. The reduce function itself can additionally take in a ‘seed’ or ‘initial value’, but this feature is optional. If a ‘seed’ is included when implementing the function, reduce will begin its “reducing” process using the ‘seed’ as the first value. Because of this, the first item in the array will be set to the current value upon which the function is iterating. It will also set the ‘accumulator’ to refer to the value of the ‘seed’ initially. If there is no seed included when reduce is called, then the accumulator will refer to the first item in the array, and the current value will be set to the second item. It passes these values into the callback function, resetting the ‘accumulator’ to the callback function’s most recent return value. It will also set the current value to the next item in the array. It will continue on this path until it has gone through each item in the array, at which point a single value will be returned. This ‘accumulator’ will be returned as it has been collecting and integrating each callback function return value into itself, resulting in a single value.

var nums = [1, 2, 3, 4, 5];var sum = nums.reduce(function(acc, curNum){
return acc + curNum;
}, 10);

console.log(sum); // => 25

In the above example, an array of numbers called ‘nums’ is initialized and it is used to call reduce. We can also see that a ‘seed’ of the number 10 is passed into the function. Beginning with 10, each number will be added to the sum of the number that came before it in the array and was previously returned by the callback function. Normally the sum of 1+2+3+4+5 would be 15, but since there is a ‘seed’ of 10, this function will resolve to, and return 25.

var animals = [
{type: 'dog', age: 12},
{type: 'horse', age: 4},
{type: 'dolphin', age: 8},
{type: 'turtle', age: 13},
];

var animalAgeSum = animals.reduce(function(ageSum, curAnimal){
return ageSum + curAnimal.age;
}, 0);

console.log(animalAgeSum); // => 37

Here we see an array of animal objects, each of which has a type and an age property. When using reduce to find the sum of the animal ages, we must include a seed of 0. This is because, if we don't provide a seed, the function will try to add the first item in the array to the value of the age property of the second item in the array. That would not work in this case, as you can’t add an object to a number. As a rule of thumb, look towards the items within the array, specifically their type of value. Then decide what type of value you want to be returned after reduce is finished. If the array items type of value differs from the type of value that is supposed to be returned, then a seed is required. If this is the case, the seed should be the same type of value that should be returned at the end.

CONCLUSION

While it may seem that passing a function to a function is a complicated and daunting task, higher-order functions can actually make the solution to a problem simpler, both in terms of writability and readability. This is what makes higher-order functions a crucial component, and invaluable resource when writing programs with Javascript.

--

--