Telerik blogs

Stuck trying to understand JavaScript’s array reduce through conventional documentation examples? In this article we explore a different approach to wrapping our head around how to use reduce—by thinking about buckets.

In my years of experience both as a JavaScript learner and afterward a teacher, I haven’t seen an API that causes more anxiety and confusion that JavaScript’s reduce. In this article I will try to approach reduce in a different way that, for some, may be a bit more intuitive and easier to understand.

I believe that part of the confusion comes from the naming of the API. One would expect that a reduce API would, in fact, always grab some sort of variable and make it smaller—which is in reality not usually the case.

Documentation such as that of MDN’s Reduce take on a cryptic approach of reducing a bunch of numbers, but in my real-world experience I think I’ve never once used array reduce to work with an array of numbers in this way.

Example from MDN:

const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  initialValue
);

console.log(sumWithInitial);
// expected output: 10

Instead, let’s flip things around. First of all, I want you to keep in mind one thing and one thing only: reduce is an API that allows us to grab an array—any array—and transform it into something else.

Thinking of Buckets

Let’s begin by looking to actual real-life example, which is to turn an array into an object! The use cases here are varied, but usually it’s used to be able to access a collection of items with an id instead of having to search the array for an object every time.

So let’s dive right into it. Pretend we have the following array:

const pets = [
 { id: 1, type: 'cat' },
 { id: 2, type: 'dog' },
 { id: 3, type: 'otter' }
]

We want to transform this array of objects into a single object which has the pet id as the key, and the whole pet as the value. So let’s get our reduce structure going.

const pets = [ ... ]

const emptyBucket = {}
const petsObject = pets.reduce(function (bucket, pet) {}, emptyBucket)

I want to draw your attention to a couple of important things. Let’s think about the concept of buckets. A bucket is “something” that you put stuff into. This bucket (in coding world) can be anything like an object, an array or even a string or number as we’ll see later.

Panda playing with a bucket

I’m being a little extra here by defining the constant emptyBucket right before we start with the reduce because I want to draw your attention to its position on the reduce call. Notice that the last parameter in a reduce call is always the bucket.

What this bucket will do is collect all of the stuff we want to put in it. Be aware that collecting stuff into objects is different than collecting stuff into arrays! Each bucket has a different way of putting things into it. But let’s look at that in a bit.

Second, take a look at the first parameter of the reduce call. It’s a function! I’ve gone ahead and written it in non-arrow function format because it may be a bit easier to follow for folks who haven’t yet learned arrow functions. If you’re curious how it would look with an arrow function format (and I will continue using this format through the rest of this article), it looks like this:

const petsObject = pets.reduce((bucket, pet) => {}, emptyBucket)

Now that we know that the last parameter is going to be our bucket, we can remove the emptyBucket constant since we don’t really need it (and it will just clutter our code).

const pets = [
 { id: 1, type: 'cat' },
 { id: 2, type: 'dog' },
 { id: 3, type: 'otter' }
]

const petsObject = pets.reduce((bucket, pet) => {
 // We will fill our bucket here
}, {}) // This is our bucket! {}

Filling Buckets

The whole point of using array reduce is to be able to modify our data, so let’s actually fill in our bucket with the data as we want it. We will transform the data as we pour it into the bucket.

const pets = [
 { id: 1, type: 'cat' },
 { id: 2, type: 'dog' },
 { id: 3, type: 'otter' }
]

const petsObject = pets.reduce((bucket, pet) => {
  // bucket is an object, so we can add a property with array syntax
  // in this case we want to be able to access a pet with it's ID
  bucket[pet.id] = pet
  
  return bucket
}, {}) // This is our bucket! {}

Wow that was not scary at all! 😎

First I want to draw your attention to the parameters that we get from the function declared inside the reduce call.

The first one—bucket—is our bucket (from line 13). If this is an object or array, you will get a pointer reference (sorry we have to get technical here), and if it’s a primitive you will get the value as it currently is in the loop. More on this in a second.

The second param—pet—is the current pet that we’re looping over. In the end you have to remember that reduce is going to loop over the array one item at a time. This will feel similar to for loops, or map, etc. where you will get the current value, the function will execute, and then the next item, and so on.

I’ve chosen to name these params bucket and pet because the cognitive load to understand them is a lot less! You will often see people name these accumulator and value or acc for short. The reason behind this is that the “bucket’s” official name is an accumulator (because it drumroll accumulates), but who cares—name it so it’s easier to read and understand.

Inside our bucket filling function, we are using array syntax (on the bucket object from line 13!) to add the pet that is currently being looped on into our bucket.

Finally, and this is a huge finally so I will put this in bold: Always return the bucket!
If the bucket is not returned, the next function will have an undefined bucket and it will explode (with an error). Pass that bucket to the next function!

Bucket of red paint exploding

Skipping Some Items

If we want to filter out or skip a particular item, we can return an unmodified bucket. This is a very handy tool when you want to check for a particular condition. Let’s modify our function to remove the otter from our pet’s object.

const petsObject = pets.reduce((bucket, pet) => {
  if (pet.type === 'otter') {
    // Return the unmodified bucket and skip the otter
    return bucket
  }
  
  bucket[pet.id] = pet
  
  return bucket
}, {}) // This is our bucket! {}

Notice that we’re are once again returning the bucket even though we didn’t actually modify it.

With this change, now when the loop executes, it will return the bucket otterless to the next loop call. If the otter is the last element in the loop, then it returns the bucket to the petObjects constant and we can use it further in our code.

Using Primitives

Now that we understand the concept of buckets, we can actually change the example to something that uses a primitive value (number, string, boolean, etc.) as a bucket. Even though these values don’t have your nice bucket-y feel, they can still be used to collect information.

For example, let’s use reduce to build a string of comma separated pet types.

const petString = pets.reduce((bucket, pet, petIndex) => {
  bucket += pet.type
  if (petIndex + 1 !== pets.length) {
    bucket += ', '
  }
  
  return bucket
}, '') // This is our bucket! It can be a string too!

In this case, our bucket is a string—notice on line 8 that our bucket declaration is an empty string. This could also potentially be an initial string such as '``I love my ' to end up with an I love my cat, dog, otter value.

Notice that this time I’ve added a third param to our function, petIndex. The third param that you can get from the reduce loop function is the index of the currently looped item. We use this index in line 3 to check if this is the last item. If it’s not, then we add a comma. We don’t want a trailing comma on our nice string.

Finally, always return the bucket.

Wrapping up

I hope that this different approach to understanding array reduce helps you wrap your head around it and unlocks a very flexible and powerful tool in your developer tool belt.

May the bucket be with you, always.


About the Author

Marina Mosti

Marina Mosti is a frontend web developer with over 18 years of experience in the field. She enjoys mentoring other women on JavaScript and her favorite framework, Vue, as well as writing articles and tutorials for the community. In her spare time, she enjoys playing bass, drums and video games.

Related Posts

Comments

Comments are disabled in preview mode.