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,currentVal
evaluates the value of thearray[0]
element (if aninitialVal
was 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: 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.