Reduce Arrays like a pro in JavaScript

7 min read views

Hello folks! It's been a while since I published an article. I was working on this very own Blog site itself.

To continue with the Higher-Order Functions series, I'll be explaining Array reduce() method in this article.

Array.reduce() is a bit hard to grasp at the beginning as compared to other Higher-Order Array methods like map(), forEach(), filter(), etc.

reduce() is just another HOF like the above mentioned methods, but what makes it difficult to understand is that it's signature/syntax and working logic is a bit different from the others.

reduce(callbackFn, initialValue)
reduce(callbackFn, initialValue)

What does the Array.reduce() method do?

reduce() method executes the user-supplied "reducer" callbackFn on each Array element, in order, passing the return value from the callback as the first parameter to the next iteration. And the result of the method is a single value returned by the last iteration.

Syntax

reduce(callbackFn, initialValue)
reduce((previousValue, currentValue, currentIndex, array) => {
  /* ... */
}, initialValue)
reduce(callbackFn, initialValue)
reduce((previousValue, currentValue, currentIndex, array) => {
  /* ... */
}, initialValue)
Parameters

callbackFn
A reducer function that executes on each element. It accepts one to four arguments :

initialValue (optional)
A value to which previousValue is initialized on the first call. If initialValue is specified, it causes the currentValue to be initialized with the first element in the array(array[0]). If initialValue is not specified, previousValue is initialized to the first element in the array(array[0]) and the currentValue is initialized to the second element in the array(array[1]).

Note: SometimespreviousValue is also referred to as accumulator as in horse racing accumulator bets.

Return Value

A single return value that results on running the "reducer" callbackFn to completion over the entire array. The return value from the last iteration of the callbackFn is the Return value of the entire method.

Simple Examples

1

Sum of Array elements is a good example to start with to understand `reduce()`

const arr = [5, 3, 6, 1]
 
const sum = arr.reduce((prev, item) => prev + item)
// sum -> 15
const arr = [5, 3, 6, 1]
 
const sum = arr.reduce((prev, item) => prev + item)
// sum -> 15

In the above snippet there's no initialValue defined, so the reducer starts with arr[0] being the prev(previousValue) and arr[1] as the value of item(currentValue) in the first iteration.

So the iterations for the reducer would look something like this without the initialValue.

callbackFn iterationpreviousValuecurrentValuecurrentIndexReturn Value
first call5318
second call86214
third call141315

For the first call, the value of previousValue is arr[0] and of currentValue is arr[1] as the initialValue wasn't specified. You can see that the previousValue for second call is initialized with the return value of the first call. This pattern goes on until the last iteration is made and the return value from the last call is the Return Value for the method.

;[5, 3, 6, 1].reduce((prev, item) => prev + item, 7) // 22
;[5, 3, 6, 1].reduce((prev, item) => prev + item, 7) // 22

The iterations looks something like this when the initialValue is passed to the method.

callbackFn iterationpreviousValuecurrentValuecurrentIndexReturn Value
first call75012
second call123115
third call156221
fourth call211322

The first call starts with initialValue initialized to previousValue and the value of arr[0] initialized to currentValue.

✨ Fun little tip that I used to do before I learnt reduce was to use eval().

eval([1, 2, 3, 4, 5].join`+`) // 15
eval([1, 2, 3, 4, 5].join`+`) // 15

Use at your own risk !!

2

Grand total of a grocery list

Let's say you were given an array of objects, of which objects contain

and you are needed to produce the grand total of the items. How would you go about finding it using reduce()?

const groceries = [
  {
    name: "Tomatoes🍅",
    price: 3.0,
    quantity: 3,
  },
  {
    name: "EggPlant🍆",
    price: 4.5,
    quantity: 5.1,
  },
  {
    name: "Broccoli🥦",
    price: 2.3,
    quantity: 0.6,
  },
]
 
const grandTotal = groceries.reduce((prev, item) => {
  const total = item.price * item.quantity
  return prev + total
}, 0)
// 33.33
const groceries = [
  {
    name: "Tomatoes🍅",
    price: 3.0,
    quantity: 3,
  },
  {
    name: "EggPlant🍆",
    price: 4.5,
    quantity: 5.1,
  },
  {
    name: "Broccoli🥦",
    price: 2.3,
    quantity: 0.6,
  },
]
 
const grandTotal = groceries.reduce((prev, item) => {
  const total = item.price * item.quantity
  return prev + total
}, 0)
// 33.33

An initialValue is provided to the reduce() method so the currentValue is going to start with groceries[0]. For the first call, the total will evaluate to 3.0*3=9 which is then summed with the previousValue a returned. The same pattern is followed until the last(third call) is made and the grandTotal results to 33.33.

More examples

Let's have a look at a bit complex examples with reduce().

1

Count items in an Array

Q. Given an array of string, count the number of occurrences of an item in the array ?

const arr = ["foo", "bar", "foo"]
 
const counts = arr.reduce((acc, item) => {
  if (acc.has(item)) return acc.set(item, acc.get(item) + 1)
  else return acc.set(item, 1)
}, new Map())
// counts -> {'foo' => 2, 'bar' => 1}
const arr = ["foo", "bar", "foo"]
 
const counts = arr.reduce((acc, item) => {
  if (acc.has(item)) return acc.set(item, acc.get(item) + 1)
  else return acc.set(item, 1)
}, new Map())
// counts -> {'foo' => 2, 'bar' => 1}

The output would have much sense if it would be in some form of object with the array items being the keys and the corresponding values being the number of occurrences of the item. I used a Map() because of the helpful methods that it has to check and set items.

In the first call the reducer is set with an initialValue of an empty Map() which is initialized to acc and the item is initialized with arr[0]. The map is checked if it contains the item, if it does, the value of the key is incremented. Otherwise in the else part, the map is set with item as the key with value of count to 1.

2

Maximum by field

Q. Given an array of objects with some numerical property, find the object with the maximum value ?

const objects = [
  { name: "foo", value: 123 },
  { name: "bar", value: 100 },
  { name: "baz", value: 150 },
]
 
const maxValueObject = objects.reduce((acc, object) => {
  if (acc.value > object.value) return acc
  else return object
})
// maxValueObject -> { name : 'baz', value: 150 }
const objects = [
  { name: "foo", value: 123 },
  { name: "bar", value: 100 },
  { name: "baz", value: 150 },
]
 
const maxValueObject = objects.reduce((acc, object) => {
  if (acc.value > object.value) return acc
  else return object
})
// maxValueObject -> { name : 'baz', value: 150 }

There is no initialValue defined for the reducer because all we need to do is compare the field of the object with the acc and return the object with maximum value to the acc, through which only the object with the maximum value stays in the acc getting compared with other objects until a maximum field value replaces it.

3

Grouping by similar value

Q. Given an array of objects with type and value keys, group the array according to the type with values.

const obj = [
  { type: "a", value: 123 },
  { type: "a", value: 100 },
  { type: "b", value: 150 },
]
 
const group = obj.reduce((acc, item) => {
  // check if the key exists
  if (!acc[item.type]) {
    acc[item.type] = [] // if not create the key initialized with empty array
  }
  acc[item.type].push(item.value) // push the value to the existing array of the key
  return acc // return the who object to next iteration
}, {})
// group -> { a: [123, 100], b: [150] }
const obj = [
  { type: "a", value: 123 },
  { type: "a", value: 100 },
  { type: "b", value: 150 },
]
 
const group = obj.reduce((acc, item) => {
  // check if the key exists
  if (!acc[item.type]) {
    acc[item.type] = [] // if not create the key initialized with empty array
  }
  acc[item.type].push(item.value) // push the value to the existing array of the key
  return acc // return the who object to next iteration
}, {})
// group -> { a: [123, 100], b: [150] }

Is this the End of the Higher-Order Function Series ?

Probably :)

cd ..

Subscribe for updatesDiscuss on TwitterSuggest Change