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 ofarray[0]element.currentVal, the element of the array in the current iteration. On first call,currentValevaluates the value of thearray[0]element (if aninitialValwas specified), otherwise - a value of thearray[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: 116Let'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.5A 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: 50If 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); // 1Case 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: 126In 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)); // TypeErrorTest 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: 165Explanation
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: 78Explanation
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: 78In 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.5Explanation
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.