Useful "reduce” use cases

I recently saw a tweet from a developer stating that he understands the array reduce function, but can not think of many useful use cases for it. In this post, I'll aim to give a couple of real-world use cases for reduce.

The post will assume you are comfortable with JavaScript Promises; if not, I have a very simplified primer on them in a previous post that you can check out.

Promise chains

Promise.all() is very useful to run numerous promises in parallel, but what if the order in which your promises should resolve is important? In that case, you need to run them sequentially. The following function will do exactly that.

const promiseQueue = (promiseFn, list) =>
  list.reduce(
    (queue, item) =>
      queue.then(async result => {
        const itemResult = await promiseFn(item)
        return result.concat([itemResult])
      }),
    Promise.resolve([])
  )

Sum numbers

The most taught and classic example of using reduce is to get the total of an array of numbers, so this list won't be complete without it. It doesn't need to be an array of numbers though - you can use more complex data structures.

const cities = [
  {
    city: 'Chongqing',
    population: 30165500,
  },
  {
    city: 'Shanghai',
    population: 24183300,
  },
  {
    city: 'Beijing',
    population: 21707000,
  },
  {
    city: 'Lagos',
    population: 16060303,
  },
  // ...etc
]

const totalPopulation = cities.reduce((sum, city) => sum + city.population, 0)

Filter and Map

When you have a data set that needs to be filtered by some criteria and you want to manipulate each remaining item as well, an okay, but slightly naive option would be to first run filter on it and then map over it again essentially doing a lot more looping-over-items than required. A better option would be to use reduce.

Say for instance you have the results of all the students that wrote a particular paper, but you are only interested in the full names of all the students that had a mark of more than 80 out of a hundred.

const studentsData = [
  {
    firstName: "Albert",
    lastName: "Einstein",
    score: 53
  },
  {
    firstName: "Charles",
    lastName: "Dickens"
    score: 84
  },
  {
    firstName: "Marilyn",
    lastName: "vos Savant",
    score: 99
  },
  // etc.
];

const smartestStudents = studentsData.reduce(
  (result, student) => {
    // do your filtering
    if (student.score <= 80) {
      return result;
    }

    // do your mapping
    result.push(`${student.firstName} ${student.lastName}`);
    return result;
  },
  []
);

Array to Object conversion

Sometimes you need an object as output from an array and not another array. All other array higher-order functions will always produce an array as output.

Say for instance you have an array of form fields with their validation constraints and you want an object of constraints where the name of the field is the top level keys in the object.

const fields = [
  {
    type: 'text',
    title: 'Title',
    name: 'title',
    constraints: {
      required: {
        message: '^Title is required',
        allowEmpty: false,
      },
    },
  },
  {
    type: 'text',
    title: 'Slug',
    name: 'slug',
    constraints: {
      required: {
        message: '^Slug is required',
        allowEmpty: false,
      },
      format: {
        pattern: '[a-z0-9_-]+',
        flags: 'i',
        message: '^Can only be a valid slug',
      },
    },
  },
  // etc.
]

const validationRules = fields.reduce(
  (rules, field) => Object.assign(rules, { [field.name]: field.constraints }),
  {}
)

This would produce something like this:

{
  title: {
    required: {
      message: '^Title is required',
      allowEmpty: false
    }
  },
  slug: {
    required: {
      message: '^Slug is required',
      allowEmpty: false
    },
    format: {
      pattern: '[a-z0-9\_-]+',
      flags: 'i',
      message: '^Can only be a valid slug'
    }
  },
  // etc.
}

Note: if you care about not mutating data, you might consider using Object.assign({}, rules, { [field.name]: field.constraints }) or using the object spread syntax, { ...rules, [field.name]: field.constraints }. I would advise against this. Personally, I'm a firm believer in the value of immutable data, but with pragmatism. In this case, a new object will be created for each iteration which could end up using quite a lot of unnecessary memory in your app.

Piping or Composing functions

Sometimes you have a sequence of functions that you want to be invoked for a specific value. The useful functional programming methods, compose and pipe are perfect for this, but unfortunately, they are not natively part of the JavaScript language (yet!). Not to worry, we can easily write our own:

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

pipe is a function that takes a list of functions, calling each function in sequence with the output of the previous function used as the input for the next.

compose is very similar; it just invokes the functions in reverse order, from last to first, so it can be implemented like this:

const compose = (...fns) => x => fns.reverse().reduce((v, f) => f(v), x)

An example could be calculating the checkout total for an online store:

const calculateTotal = pipe(
  applyAnySales,
  minusStoreCredit,
  applyCouponCode,
  addTaxes
)

const cardValue = 250

const total = calculateTotal(cardValue)

If you have any other practical examples of using reduce please leave a comment so that I can learn from you.

Discuss on Twitter