My FeedDiscussionsHeadless CMS
New
Sign in
Log inSign up
Learn more about Hashnode Headless CMSHashnode Headless CMS
Collaborate seamlessly with Hashnode Headless CMS for Enterprise.
Upgrade ✨Learn more
Higher-Order Functions in JavaScript

Higher-Order Functions in JavaScript

Gaurav Kumar Dey's photo
Gaurav Kumar Dey
·Jun 13, 2021·

6 min read

If you have come from a programming language like C++, you would it crazy that in JavaScript we can pass a function into another function as an argument and a function can even return another function.

Amazed like this cat right ?? EQYyW3aX0AANz6O.jpg

Introduction

In Javascript, functions are treated as values and are referred to as first-class citizens. This means that they can be assigned to a variable and/or passed as a value.

let functionVarible = function(num){
       return num * 10;
}
functionVarible(10) //100 is returned

This piece of code is the basis of how javascript treats its function and this the basis of functional programming.

Higher-order function

Higher-order functions are functions that take other functions as arguments or return functions as their results, yes it's that simple.

The functions that are passed to a function as a parameter are known as Callback function, its called so because the higher-order function calls this function inside the function scope.

function greet(){
      return "Hello";
}

function higherOrderFun(callback){
    return callback() + " World"
}

console.log(higherOrderFun(greet)) //Prints hello world

In this code snippet, the great function is sent to the higherOrderFun function as a callback and it returns a string.

Now that we understood a higher-order function, let's understand why we use these concepts and their advantages.

Lets us assume we want to make a function to calculate areas of radius given to us in an array.

const radius = [1,2,3,4];
function calculateArea(radiusArr) {
  const output = [];
  for (let i = 0; I < radiusArr.length; ++i) {
    output.push(Math.PI * radiusArr[i] * radiusArr[i]);
  }
  return output;
}

console.log(calculateArea(radius));

Now if wish to calculate the circumference for the radius array, generally what we tend to do is that we copy and paste the previous code and change the insertion logic as:-

function calculateCircumference(radiusArr) {
  const output = [];
  for (let i = 0; I < radiusArr.length; ++i) {
    output.push(2 * Math.PI * radiusArr[i] );
  }
  return output;
}

console.log(calculateCircumference(radius));

This code works 100% but if you tend to do the same in an interview just copy-pasting stuff here and there it makes a bad impression on the interviewer.

Same functions in an optimised way:-

const radius = [1,2,3,4];

function areaLogic = function(radius){
     return Math.PI * radius * radius;
}

function circumferenceLogic = function(radius){
     return 2 * Math.PI * radius;
}

function calculate(radiusArr, logic) {
  const output = [];
  for (let i = 0; I < radiusArr.length; ++i) {
    output.push(logic(radiusArr[i]));
  }
  return output;
}

console.log(calculate(areaLogic ,radius));
console.log(calculate(circumferenceLogic,radius));
console.log(calculate(someOtherLogic,radius));
....

Here we abstracted the logic out and made a single calculate function to use it with as many logic functions without repetition of any code. This principle is known as DRY principle abbreviated as Don't Repeat Yourself

Why functional programming is preferred?

Functional programming emphasizes making small reusable functions to perform a particular task, which makes our code modular and readable.

Common higher-order functions

These functions give a lot of power to the developer community which makes coding much easier.

lptavijiiex21.jpg

1. Map function

As it's a higher-order function it takes a function as an argument and returns an array. A simple map function:-

const numbers= [1,2,3,4,5];
const doubled  = numbers.map(n => n * 2);
console.log(doubled); // [2,4,6,8,10]

map() is much similar to the forEach() as in working but .forEach() is not a higher-order function. This is because of the 3 differences:

  1. map() doesn't mutate the given array and works with a copy of it and returns an array while forEach() mutate the given array which makes it an impure function.

  2. map() returns an array so, it can be stored in a variable but forEach() return void so it cannot be stored in a variable.

  3. we can append other functions after map() as arr.map(callback).filter(filterFun) but we cannot do it for forEach() function.

Let's say we have an array of objects and we need to change the price of rice to 40

const arrObj = [
  { item: "rice", price: 20 },
  { item: "pulse", price: 30 },
  { item: "potato", price: 40 },
  { item: "onion", price: 50 },
];

Solution

const updatedArr = arrObj.map((currItem) =>
  currItem.item === "rice" ? { ...currItem, price: 40 } : currItem
);

It looks hard but what happens is the map function iterates over the array of objects and compares each object if it has a value of rice for the item key, and on matching the object price key-value pair is replaced with price:40 and the rest is returned as it is.

2. Filter function

As the name suggests it returns an array filtering with the condition given.

const numbers = [1,2,3,4,5];
var evenNumbers= numbers.filter(n => n % 2 == 0);

3. Reduce function

Just like .map() the .reduce() also runs a callback for each element of an array but reduce passes the result of this callback (the accumulator) from one array element to the other.

The accumulator can be anything like integer, string, object, etc. and must be instantiated or passed when calling .reduce().

A simple reduce function:-

const numbers = [1,2,3,4,5];
var initialVal = 0;
const sumOfNumbers = numbers.reduce((accu, val) => val + accu , initialVal);
console.log(result) // 15

Let's take a complex example as we did for the .map(), let's assume we have an array of groceries and we need to find the total bill.

Solution

const arrObj = [
  { item: "rice", price: 20 },
  { item: "pulse", price: 30 },
  { item: "potato", price: 40 },
  { item: "onion", price: 50 },
];
const bill = arrObj.reduce((acc,item) => acc + item.price , 0) 
console.log(bill); // 140

Combining map(), filter() and reduce()

Now, as we know all the 3 concepts, let's combine them to create some magic. Here we will use .filter() to filter out the items whose price are equal to greater than 40 then .map() to add the vegetable property if it's a vegetable and .reduce() to finally create a sum of the age of everyone combined.

const arrObj = [
  { item: "rice", price: 20 },
  { item: "cabbage", price: 30 },
  { item: "pulse", price: 30 },
  { item: "potato", price: 40 },
  { item: "onion", price: 50 },
  { item: "wheat", price: 60 },
];
const vegetables = [ "cabbage", "potato", "onion"];
const priceAbove40 =  (item) => item.price >= 40;
const isVegetable = (item) => item.vegetable === true
const addVegetableProperty = (item) => { item.vegetable = vegetables .includes(item.item);  return item;}
const priceReducer = (sum, item) => { return sum + item.price}, 0);
const vegetableAbove40= arrObj.filter(priceAbove40).map(addVegetableProperty).filter(isVegetable )
const totalPricevegetableAbove40 =  vegetableAbove40.reduce(priceReducer) //90

This was all about the higher-order functions. Hope you have learnt a lot. Stay tuned for more such articles. Please share and comment below your views on this topic.

Connect with me on LinkedIn