What it is for

The array reduce() method allows you to turn an array into any other value using a passed callback function and an initial value. The callback function will be called for each element in the array and must always return a result.

Syntax

reduce(function(accumulator, currentVal, currentIndex, arr) { ... }, initialVal)

The reduce method utilizes two parameters: a callback function and initialVal - an initial value for the accumulator. The initial value is a value to which the accumulator is initialized upon the first callback. 

If initialVal is specified, that also causes currentVal to be initialized to the first value in the array. If initialVal is not specified, the accumulator is initialized to the first value in the array, and currentVal is initialized to the second value in the array. 

The callback function itself can use four parameters:

  • accumulator, the current value of the accumulator. If the initial value is not specified, it will evaluate to a value of array[0] element.

  • currentVal, the element of the array in the current iteration. On first call, currentVal evaluates the value of the array[0] element (if an initialVal was specified), otherwise - a value of the array[1] element.

  • currentIndex, the index of the current element.

  • arr, the array itself that we are iterating over.

Example

const numbers = [11, 12, 13, 14, 15, 16, 17, 18];
// Find the sum of the elements
const sum = numbers.reduce(function (accumulator, currentNumber) {
return accumulator + currentNumber;
}, 0);
console.log(sum); // Output: 116

Let's look more closely.

  • On the first run, the last argument is 0 and the first element in the array equals 11. So the function results in 11.

  • On the second run, sum equals 11, and to it we add the second element of the array (12).

  • On the third run, sum equals 33, to which we add the next item, and so on.

Using reduce is similar to forEach(), map(), and filter() methods, which also utilize  the callback function. However, reduce has an additional argument, which is the current accumulated value. The function must return a value because in each subsequent iteration, the value in the accumulator will be the result that was returned in the previous step. 

The reduce method is extremely useful when we want to compute a new value by manipulating the values ​​of an array. Thus, we have a powerful tool for data processing, for example, it can be calculating the sum of values ​​in an array or grouping it into other data types.

Example

const numbers = [11, 12, 13, 14, 15, 16, 17, 18];
// Dont forget accumulator goes first!
function findAverageVal(accumulator, currentVal, currentIndex, arr) {
  const sum = accumulator + currentVal;
  // Calculate the average value by dividing the accumulator value by the number of elements
  if (currentIndex === arr.length - 1) {
    return sum / arr.length;
  }
  return sum;
}
const averageVal = numbers.reduce(findAverageVal, 0);
console.log(averageVal); // Output: 14.5

A logical question that may arise here is what is the value of the accumulator during the first iteration? It depends whether one has specified the initial value argument in the callback function or not. Let's look closer at these cases.

Special cases

Case 1. The initial value specified 

If the initial value is specified and the array isn't empty, the reduce() method invokes the callback function starting at 0 index.

The accumulator  will be equal to the initial value and the current value will be equal to the first value in the array. 

[1, 100].reduce((x, y) => Math.max(x, y), 50); // Output: 100
[50].reduce((x, y) => Math.max(x, y), 10); // Output: 50

If the array has only one element and no initial value is provided, or if the initial value is provided but the array is empty, the result will be returned without the callback function

// callback will not be invoked
[50].reduce((x, y) => Math.max(x, y)); // Output: 50
[].reduce((x, y) => Math.max(x, y), 1); // 1

Case 2. The initial value not specified

Upon such a condition, the accumulator is equal to the first array element value, while the current value will be equal to the value of the second argument.

const arrForReduce = [31, 42, 53];
const total = arrForReduce.reduce(function (accumulator, value) {
  return accumulator + value;
});
console.log(total); // Output: 126

In the fragment above, the accumulator at the first iteration is 31, and value is 42. Then, 53 is added to the accumulated value 73, and the result is returned.

Case 3. The initial value with an empty array not specified

If an array is empty, JavaScript will throw a TypeError: "Reduce of the empty array with no initial value". This case needs to be handled separately, for example by wrapping reduce in the try...catch statement, but it's better to always specify an initial value.

[].reduce((x, y) => Math.max(x, y)); // TypeError

Test yourself

  • Task 1. Sum of all values of an array

Goal: get the sum of all elements of the array.

const myArr = [11, 22, 33, 44, 55];

Solution:

let totalVal = myArr.reduce(
  (accumulator, currentVal) => accumulator + currentVal,
  0
);
console.log(totalVal); // Output: 165

Explanation

Here we have our initial array of numbers. Then we declare the variable totalVal and assign it to the reduce() call on our initial array. Finally, we log our result via console.log().

  • Task 2. Sum of values in an object array

Goal: calculate the amount of money in all accounts.

const bankAccounts = [
  { id: "423", amount: 20 },
  { id: "545", amount: 15 },
  { id: "667", amount: 35 },
  { id: "889", amount: 8 },
 ];

Solution:

const totalAmount = bankAccounts.reduce(
  // sum is an accumulator here,
  // we store the intermediate value in it
  function (sum, currentAccount) {
    // We take the current value for each iteration.
    // and add it with the amount of money
    // from the current account
    return sum + currentAccount.amount;
  },
  // Initial value,
  // which the accumulator initializes
  0
);
console.log(totalAmount); // Output: 78

Explanation

We call the reduce() method on our totalAmount array, and define a callback function with 2 arguments - sum and currentAccount

sum is an accumulator here, and currentAccount in each iteration will take the amount from the element and add that value to the accumulator - the sum variable in our case. To get a better understanding, you can look at the code that does the same thing, but without reduce().

We determine where we will store the amount, in our case it is the totalAmount variable where we define the initial value of the accumulator. Then we'll use the for statement to iterate the bankAccounts array. Each time we take the new element of the array, extract the amount and add it to our totalAmount variable. 

let totalAmount = 0;
//totalAmount variable is an accumulator here
for (let i = 0; i < bankAccounts.length; i++) {
  const currentAccount = bankAccounts[i];
  // In each iteration, add
  // to the current amount, the amount of money in the account
  totalAmount += currentAccount.amount;
}
console.log(totalAmount); // Output: 78

In both examples, we have an accumulator where the current value is stored and a new one to calculate a new value. Only reduce allows us to do this in one place and in a more understandable declarative style.

  • Task 3. Create a new object from the object array

Goal: Create a new object with a key as id and a value as a nickName.

const webUsers = [
  { id: "1", nickName: "Bilbo" },
  { id: "2", nickName: "Freyja" },
  { id: "3", nickName: "Tor" },
];

Solution:

const webUsersById = webUsers.reduce(function (result, user) {
  return {
    ...result,
    [user.id]: user.nickName,
  };
}, {}); // Output: { '1': 'Bilbo', '2': 'Freyja', '3': 'Tor' }

Explanation

We define result and user variables in the callback function. Also in the callback function we define an empty object as an initial value and will add the result from each iteration to this object. In each iteration our user variable will be assigned to the target element of the webUsers array and will extract id and nickName from it. In this case we use destructuring to collect results.

  • Task 4. Link multiple arrays

Goal: join all arrays into a single array.

const myArr = [
  [5, 9],
  [28, 13],
  [45, 105],
];

Solution:

let joinedArray = myArr.reduce(function (accumulator, currentVal) {
  return accumulator.concat(currentVal);
}, []);
console.log(joinedArray); // Output: [ 5, 9, 28, 13, 45, 105 ]

Explanation

We call the reduce() method on the given myArr array. In the callback function we define an empty array as the initial value and store data from each iteration there. Then we define a callback function with two arguments - accumulator and currentVal. 

In the first iteration the accumulator will be assigned to the empty array and the currentVal will be the value of the element with 0 index in myArr. In each iteration the values of the included array in myArr will be added to the accumulator. Finally, we'll have an array of numbers.

  • Task 5. Count instances of values in an object

Goal: count all values in an object.

let countedUserNames = userNamesArr.reduce(function (countNamesArr, name) {
  if (name in countNamesArr) {
    countNamesArr[name]++;
  } else {
    countNamesArr[name] = 1;
  }
  return countNamesArr;
}, {});
console.log(countedUserNames); // Output: { Don: 2, Ellie: 1, Sara: 1, Din: 1 }

Solution:

let countedUserNames = userNamesArr.reduce(function (countNamesArr, name) {
  if (name in countNamesArr) {
    countNamesArr[name]++;
  } else {
    countNamesArr[name] = 1;
  }
  return countNamesArr;
}, {});
console.log(countedUserNames); // Output: { Don: 2, Ellie: 1, Sara: 1, Din: 1 }

Explanation

We call reduce() method on the userNamesArr array. In the callback function we define two arguments: countNamesArr which refer to the accumulator, and name variables. Also we define an empty object as an initial value. 

Then we check the target name in our new object using the in operator (remember the syntax: key in object). If it is right, we add 1 to the current counter of this name, if not - create a new element with name as a key and a counter as a value, and assign the counter with the value 1. Finally, we'll have a new object with names as keys and name counters as values.

  • Task 6. Remove duplicate items in an array

Goal: Remove duplicate letters from an array.

let initialArr = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];

Solution:

let initialArrWithNoDuplicates = initialArr.reduce(function (
  accumulator,
  currentVal
) {
  if (accumulator.indexOf(currentVal) === -1) {
    accumulator.push(currentVal);
  }
  return accumulator;
},
[]);

console.log(initialArrWithNoDuplicates); // Output: [ 'a', 'b', 'c', 'e', 'd' ]

Explanation

We call reduce() method on the initialArr array. In the callback function we define two arguments: accumulator, and currentVal variables. Also we define an empty array as an initial value. Then we check whether the current element is in our new array or not. 

For this operation we have our current element index -1. If it is assigned as true, it means we do not have this element in our new array. So we push that element to the new array. If the check returns false, we skip that element and do nothing with it. Finally, we'll have a new array with the unique values.

  • Task 7. Replace filter() and map() with reduce()

Goal: You'll be given an array of numbers. Choose even numbers, calculate their values squared and select numbers greater than 50 from them.

const numbersArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

Solution: using reduce()

function filterEven(num) {
  return num % 2 === 0;
}
function square(num) {
  return num * num;
}
function filterGreaterThanFifty(num) {
  return num > 50;
}
const finalResult = numbersArr.reduce(function (res, num) {
  if (filterEven(num)) {
    const squared = square(num);
    if (filterGreaterThanFifty(squared)) {
      res.push(squared);
    }
  }
  return res;
}, []);
console.log(finalResult);

Explanation

Firstly, we define 3 functions for each mathematical operation. filterEven function for extracting even numbers, square function for squared calculation and filterGreaterThanFifty to select numbers exceeding 50. 

Then we call the reduce() method on the numbersArr, define res argument as the accumulator and num as the current value. The initial value is an empty array. Then in our iteration the num variable takes the value of the target element and we apply our defined functions to perform the task. 

Finally, if the current element satisfies all 3 conditions, we add it to the new array. Here is a solution using map() and filter().

const finalResult = numbersArr
  .filter(filterEven)
  .map(square)
  .filter(filterGreaterThanFifty);
  • Task 8. Function composition enabling piping

Goal: Increment, then decrease, then triple and finally half a given number 10.

function increase(input) {
 return input + 1;
}
function decrease(input) {
 return input - 2;
}
function triple(input) {
 return input * 3;
}
function divide(input) {
 return input / 2;
}

Solution:

const pipeline = [increase, decrease, triple, divide];
const finalResult = pipeline.reduce(function (accumulator, func) {
 return func(accumulator);
}, 10);
console.log(finalResult); // Output: 13.5

Explanation

We use the reduce method to create a pipeline. A pipeline is a term used for a list of functions that transform initial value into final value. Our pipeline consists of four functions in the order of application. 

For the task at hand, we declare the function under our initial value. If we want to increment, double or decrement, we just alter the pipeline. Then we call the reduce() method on the pipeline. 

In the callback function we define 2 arguments - accumulator and func. As an initial value we specify the value we'd like to manipulate. The function will call out initial values one by one. Finally, we'll have a new value of the function from the pipeline.

The array reduce() method allows you to turn an array into any other value using a passed callback function and an initial value. The callback function will be called for each element in the array and must always return a result.