DEV Community

Kirk Shillingford
Kirk Shillingford

Posted on

Reduce in 5 Minutes

Here's a quick intro to the reduce() method in Javascript/Typescript arrays, which is frequently perplexing when encountered in working code.

The code here is written in Typescript but I've tried to keep it friendly for JS readers, and I'll post a link to the equivalent JS at the end.

What's the point of reduce?

Reduce allows us to take a container of data (like an array) and fold it into another data structure.

The reduce() method involves three parts:

  • A container of values, like an array, with which we need to update another structure (in sequence)
  • A function that lets us update a value (typically called the accumulator) based on an element from our array
function updater(accumulator:SomeType, nextValueFromArray): SomeType {
    ... // whatever operations we want
    return updatedAccumulator
}
Enter fullscreen mode Exit fullscreen mode

Frequently this updater is written inline, directly inside the reduce function.

  • The last thing the reducer needs is an initial value for our accumulator, for the first iteration of the function. Reduce is smart enough to realize that if we don't provide an initial value, it should use the first element of the array as the initial value.

NOTE: Omitting the initial value only works if the accumulator is the same type as elements. An example to show this will be provided below.

So, to recap, any reduce operation can be thought of as

someArrayOfValues.reduce(updater, initialValueOfTheAccumulator)
Enter fullscreen mode Exit fullscreen mode

Examples

Let's look at some examples!

First, let's see how we could do string concatenation using reduce. This involves 'folding' an array of strings into a single string.

// our array of characters to fold
const boSpelling = ['B', 'o', ' ', 'B', 'u', 'r', 'n', 'h', 'a', 'm']


// our initial value for us to reduce into is an empty string 
const initialName = ''
Enter fullscreen mode Exit fullscreen mode

Here you see, we write a function that understands how to add a letter to a value and return a new value. Reduce takes that function, and our new value, and will pass each letter of our array to that function, bringing the result forward to serve as the accumulated value for the next iteration.

const bosName = boSpelling.reduce((nameSoFar, letter) => {
    const updatedName = nameSoFar + letter
    return updatedName
}, initialName)
Enter fullscreen mode Exit fullscreen mode

We could also inline the initial value.

const bosName = boSpelling.reduce((nameSoFar, letter) => {
    const updatedName = nameSoFar + letter
    return updatedName
}, '')

console.log(bosName) // "Bo Burnham" 
Enter fullscreen mode Exit fullscreen mode

Just to provide context, here is the for loop version. This does the same as the code above, but here we update a mutable variable and use a for block instead of a function expression.

Some people find this preferable but it does require object mutation, unlike reduce.

const concatenate = (lst:string[]) => {
    let name = ""
    for (let letter of lst) {
        name += letter
    }
    return name
}

const bosName = concatenate(boSpelling)

console.log(bosName) \\ "Bo Burnham"
Enter fullscreen mode Exit fullscreen mode

Now, let's make a custom sum function using reduce. Combining with es6 syntax allows for some very concise expressions.

const numbers = [ 2, 3, 4, 5, 6, 7, 8, 9, 10]

const sum = (lst:number[]) => 
    lst.reduce((count, nextNum) => count + nextNum, 0)
Enter fullscreen mode Exit fullscreen mode

Note that since the accumulator count, and the array elements are both numbers, we can omit the initial value and just let reduce use the first value as the initial.

In situations where they're not the same data type, doing this would cause an error.

const sum = (lst:number[]) => 
    lst.reduce((count, nextNum) => count + nextNum)

console.log(sum(numbers)) // "54"
Enter fullscreen mode Exit fullscreen mode

Advanced Examples

We've hit the end of the main things I wanted to demonstrate with reduce (I told you this would be quick). But we can have a bit more fun and show how powerful and flexible it really is. These next examples are beyond the standard use cases of reduce and if you're still new to the concept, feel free to skip them.

The reduce() method can fold a sequence of values into any data structure you'd like, including another sequences.

This makes it more powerful than its sibling methods, map() and filter(), which can only transform an array into another array. But that doesn't mean it can't do what they do as well.

Here we make map() from reduce. For each item in the original array, we apply the function to it and add to an accumulator, a new array.

const map = <a, b>(func:(arg:a) => b, lst:a[]) => 
    lst.reduce((acc:b[], item) => [...acc, func(item)], [])
Enter fullscreen mode Exit fullscreen mode

and we can use it similarly to the map() method we know

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

const multiplyByThree = (x:number) => x * 3

const mapResults = map(multiplyByThree, numbers)

console.log(mapResults) \\ "3,6,9,12,15,18,21,24,27,30"

Enter fullscreen mode Exit fullscreen mode

The filter function is similar. Here, the function we pass in is a condition, which is a function that accepts a variable of the same type as the ones in the array and returns a boolean).

If the array item satisfies the condition (returns true), we add it to the new array, otherwise we pass along the new array as is.

const filter = <a>(condition:(arg:a) => boolean, lst:a[]) => 
    lst.reduce((newLst:a[], item) =>
        condition(item) ? [...newLst, item] : newLst, [])

// our condition
const isEven = (x:number) => x % 2 === 0 ? true : false

const filterResults = filter(isEven, numbers)

console.log(filterResults) \\ "2,4,6,8,10"
Enter fullscreen mode Exit fullscreen mode

A brief aside on types

Another way we can compare the three methods in in terms of the types they accept, and return. In pseudocode, the types of the three functions can be described as

map : (a -> b), Array a -> Array b
Given a function that takes an a and returns a b, and an array of a, map will return an array of b.

filter : (a -> Bool) -> Array a -> Array a
Given a function that takes an a and returns a boolean, and an array of a, filter returns an array of a

reduce : (b -> a -> b) -> b -> Array a -> b
Given a function that takes a b and an a and returns a b, an initial accumulator value b, and an array of a, reduce returns a b.

Final Thoughts

I hope this provided some clarity and demystified one of the more powerful tools in the JS toolbelt.

Let me know if this helped, or what other methods you want five minutes on!

Resources

  • See here for the full code in a sandboxed environment, in both TS and JS versions.
  • See here for some more official docs on the method, it's overloads, etc.
  • Note there's a small error in the live example where the isOdd function actually checks for evenness, and I am being too lazy to fix it and get a new URL.

Discussion (1)

Collapse
devparkk profile image
Dev Prakash

Wonderful man 👍