DEV Community

Akshendra Pratap Singh
Akshendra Pratap Singh

Posted on

The beast that is Array.prototype.reduce

reduce() is an absolute beast of a method when it comes to functional style programming in JavaScript. The more you use it, the more you see use cases popping everywhere for it.

I recently realized, that it has become my goto method, whenever I have to deal with arrays. So I looked through bunch of my code and found a lot of examples, some of which I will list in this post. But before that - lets start with a short review the method itself.

Signature

arr.reduce((acc, current, index, array) => {
  // work goes here
}, initial);
Enter fullscreen mode Exit fullscreen mode

reduce() takes two parameters.

  • A callback function, would be the first. reduce() will go through every element of the array and pass callback the following values.
    • acc or accumulator, this value is like state that gets updated on every call to keep track of the result
    • For the first call, it is equal to initial value provided as second parameter.
    • And in subsequent calls, acc will be the value returned by the previous callback call.
    • current, the element of the array we are dealing with.
    • index, the current index of array
    • array, the array itself
  • The second parameter is initial, the first value of acc. This is optional and in case it is not provided, acc will be the first element of the array.

Simple example

A very common example of reduce() is to calculate the sum of an array of integers.

[1, 2, 3, 4, 5].reduce((sum, integer) => sum + integer, 0);
Enter fullscreen mode Exit fullscreen mode

In this example, we don't need index and array, which is a case in general with reduce(). And sum, integer and 0 play the parts of acc, current and initial respectively.

Now some practical examples

I mentioned above that I went through some of my code to find examples of reduce(). I have listed below some of those, which were different enough to represent a new use case.

I have trimmed the project specific code from these examples to keep the short

1. Reducing to a boolean

I have a file path (id) and I want to know, if the path belongs to any of the directories or files from the watching array.

return watching.reduce((acc, curr) => {
  return acc || id.startsWith(path.join(__dirname, curr));
}, false);
Enter fullscreen mode Exit fullscreen mode

2. Converting an array of objects into a map using a specific property / key of the objects

I have an array of objects that I received from a database. But I want to convert them into a simple map for later processing. All these objects have a common structure and a key that stores a unique identifier (primary key).

Example of data,

// docs array
const docs = [{
  id: 'id-1',
  name: 'K Dilkington',
  style: 'orange',
}, {
  id: 'id-2',
  name: 'Lanky Fellow',
  style: 'googly',
}];

// result
const result = {
  'id-1': {
    id: 'id-1',
    name: 'K Dilkington',
    style: 'orange',
  },
  'id-2': {
    id: 'id-2',
    name: 'Lanky Fellow',
    style: 'googly',
  },
};
Enter fullscreen mode Exit fullscreen mode
function makeMap(docs, key) {
  return docs.reduce((map, doc) => {
    map[doc[key]] = doc;
    return map;
  }, {});
}
Enter fullscreen mode Exit fullscreen mode

We can now call the this function using makeMap(docs, 'id'), to build the map we desire.

3. Flatten an array of arrays

A very common case. I have an array of arrays and I want to combine them into a single array.

function flatten(arr) {
  return arr.reduce((acc, current) => {
    return acc.concat(current);
  }, []);
}


flatten([['1', '2'], ['3', 4], [{}, []]]) // => [ '1', '2', '3', 4, {}, [] ]
Enter fullscreen mode Exit fullscreen mode

4. Doing the job of filter() - quite unnecessary :)

From an array of players, filter those with with valid ids (mongoId here).

game.players.reduce((acc, val) => {
  if (is.existy(val.mongoId)) {
    acc.push(val.mongoId);
  }
  return acc;
}, []);
Enter fullscreen mode Exit fullscreen mode

5. A deep Object.assign

Object.assign copies values from source objects to given object, but it does a shallow copy and also mutates the given object.

I want a function (deepAssign), that would do a deep copy and would not mutate the given object.

const source = {
  l1: {
    inside: true,
    prop: 'in',
  },
  prop: 'value',
};
const target = {
  prop: 'out',
  l1: {
    prop: 'inisde',
  },
}

const shallow = Object.assign(source, target);
/*
shallow = {
  "l1": {
    "prop": "inisde"
  },
  "prop": "out"
}
*/

const deep = deepAssign(source, target);
/*
deep = {
  "l1": {
    "inside":true,
    "prop": "inisde"
  },
  "prop": "out"
}
Enter fullscreen mode Exit fullscreen mode
function deepAssign(object, update, level = 0) {
  if (level > 5) {
    throw new Error('Deep Assign going beyound five levels');
  }

  return Object.keys(update).reduce((acc, key) => {
    const updatewith = update[key];
    if (is.not.existy(updatewith)) {
      return acc;
    }

    // lets just suppose `is` exists
    if (is.object(updatewith) && is.not.array(updatewith)) {
      acc[key] = deepAssign(object[key], updatewith, level + 1);
      return acc;
    }

    acc[key] = updatewith;
    return acc;
  }, Object.assign({}, object));
}
Enter fullscreen mode Exit fullscreen mode

We are using recursion here and don't want to kill the stack, hence a simple check for - how many levels deep inside the source object we should care about.

6. Chaining Promises

I have four async functions that have to be executed in series, feeding the result of the previous function into next.

const arr = [fetchData, updateData, postData, showData];
const response = arr.reduce((acc, current) => {
  // (cue alarm sirens) no error handling
  return acc.then(current));
}, Promise.resolve(userId));

response.then(data => {
  // data is final response
});
Enter fullscreen mode Exit fullscreen mode

That's it folks.

I found several more examples, however they were following more or less the same storylines with a twist or two of their own.

Finally, thanks for reading and if you have any magical use case of reduce() or if I have made any mistake in this post, I would love to know.

Top comments (5)

Collapse
 
malcolmkee profile image
Malcolm Kee

You can use reduce to implement map, filter, etc. That's why some people call reduce as the most important operation of array.

Besides, just to add on, your example of reducing to a boolean can be replaced with Array.prototype.some method.

Collapse
 
kayis profile image
K

Yes, reduce is actually an important building block of FP.

Collapse
 
akshendra profile image
Akshendra Pratap Singh

Thanks for reminding me about .some(). I always forget that it exists.

Collapse
 
itsasine profile image
ItsASine (Kayla)

Methods like reduce and map always confuse me because the documentation is ultra simple (like summation) while the tech community adores them for being powerful and versatile...

Your post was the first I've seen to approach it with practical examples while still being approachable to someone learning it for the first time. Great post!

Collapse
 
tompearson profile image
Tom Pearson

I'm a big fan of reduce but there's often a simpler solution, or at least one which makes for more legible code. For example you can flatten an array of arrays using concat and restructuring e.g.

const a = [[1,2,3,4,5],[1,2,3,4,5]];

const flattened = [].concat(...a);
// flattened will be ... [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

As a general policy I try to name my reducer function arguments to reflect what they're doing in the specific case rather than just giving them general names (as you do in your examples i.e. map vs acc for the 'previousValue' argument).