JavaScript Array.reduce() by@jlowery2663

JavaScript Array.reduce()

JavaScript Array.reduce() method has been around awhile. It operates upon an array of values by invoking a callback function. It doesn’t do what you expect. It can do the opposite of reduce, in fact. It’s misnamed. The name reduce would lead one to believe that it reduces an array to a subset of those elements. It doesn't even have to return an array. It can even add an accumulator initial value as a second argument to the reduce function.
image
jefflowery Hacker Noon profile picture

jefflowery

I'm an early adopter kinda guy.

How do I loathe thee? Let me count the ways…

The Array.reduce() method has been around awhile. Like map(), filter() and find(), it operates upon an array of values by invoking a callback function.

Here’s an example from the developer.mozilla.org site:

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15

Seems straightforward, right? On with the grievances:

0. It doesn’t do what you expect

Let’s look at this again:

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// expected: 1 + 2 + 3 + 4 === 10

Now I’ll modify the reducer function to add 1 to each value of the array, then sum it with the previous value. Since I’m adding 1 to four array elements, I expect to get a sum of 14.

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue + 1;
// (1+1) + (2+1) + (3+1) + (4+1) = 2+3+4+5 = 14
console.log(array1.reduce(reducer));
// unexpected output: 13

Hold on… what?

Turns out the first element of the array is the initial accumulator value. So, instead of

(1+1) + (2+1) + (3+1) + (4+1) = 2+3+4+5 = 14

It is actually:

1 + (2+1) + (3+1) + (4+1) = 2+3+4+5 = 13

All those reduce examples online that use a sum function as illustration can mislead you. You can get to the right answer, of course: just add an accumulator initial value as a second argument to the reduce method:

console.log(array1.reduce(reducer, 0));
// desired output: 14

1. It’s misnamed

The name reduce would lead one to believe that it reduces an array of element to a subset of those elements. That’s not really what it does. It can do the opposite of reduce, in fact:

const array1 = [1, 2, 3, 4];  // four elements
const increaser = (accumulator, currentValue) => {
  accumulator.push(currentValue);
  accumulator.push(currentValue*currentValue);
  return accumulator;
};
console.log(array1.reduce(increaser, []));
// output: Array [1, 1, 2, 4, 3, 9, 4, 16] (8 elements)

And it doesn’t even have to return an array:

const array1 = [1, 2, 3, 4];
const transformer = (accumulator, currentValue) => {
  accumulator[currentValue] = currentValue*currentValue;
  return accumulator;
};
console.log(array1.reduce(transformer, {}));
// output: Object { 1: 1, 2: 4, 3: 9, 4: 16 }

2. The order of callback arguments is weird

Let’s compare the callbacks of map(), filter(), find(), and reduce():

map: (currentValue, index, array, this) => {}

filter: (currentValue, index, array, this) => {}

find: (currentValue, index, array, this) => {}

reduce: (accumulator, currentValue, index, array) => {}

Your callback will get the accumulator as the first argument, not the currentValue; also, there is no parameter that takes a this argument.

3. Error: myVar.reduce is not a function

I’ve been spoiled by lodash, which treats both arrays and objects as collections, and collections have map, find, filter, and reduce methods. Not so JavaScript reduce:

var obj1 = {my:"dog", has: "fleas"};
            
const values = (accumulator, currentValue) => accumulator.push(currentValue);
            
console.log(obj1.reduce(values, []));
// > Error: obj1.reduce is not a function

As Dana Carvey used to say, “Nope, not gonna do it. Wouldn’t be prudent.” (Yes, I am that old.)

You can still do something like this, using Object.values or Object.entries:

var obj1 = {my:"dog", has: "fleas"};
            
const values = (acc, [key, value]) => {acc.push(value); return acc}
console.log(Object.entries(obj1).reduce(values, []));
// > Array ["dog", "fleas"]

4. Accumulator sometimes optional; results sometimes random

It’s easy to forget an initial accumulator value, even when you need one. Let’s take that above example, with one modification:

var obj1 = {my:"dog", has: "fleas"};
            
const values = (acc, [key, value]) => {acc.push(value); return acc}
console.log(Object.entries(obj1).reduce(values));
// > Array ["my", "dog", "fleas"]

Did you notice the difference in the code? Hard to spot. What’s worse, you get an array of values back, except the first is invalid. Good thing I have tests someday!

5. Undefined? Huh?

Oh, you forgot to return the accumulator. Silly you. I never make that mistake:

var obj1 = {my:"dog", has: "fleas"};
            
const dog = (acc, [key, value]) => {
  if (key === 'my') {
    acc.push(value);
    return acc;
  }
}
console.log(Object.entries(obj1).reduce(dog, []));
// > undefined

You have to always return the accumulator:

var obj1 = {my:"dog", has: "fleas"};
            
const dog = (acc, [key, value]) => {
  if (key === 'my') {
    acc.push(value);
  }
  return acc;
}
console.log(Object.entries(obj1).reduce(dog, []));

So what do I recommend instead of reduce()?

I am always using Array.reduce(), and you should, too. Just know its quirks. It’s like a tiny chihuahua nipping at your heels that sometimes will get you if you’re not paying attention, but it’s mostly harmless (and far more useful than a chihuahua).

image

Happy reducing!

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.