Register to get access to free programming courses with interactive exercises

Reducing JS: Functions

The last function of our three is the reduce() method (say "convolution"), which is used to aggregate data. Aggregation refers to an operation that calculates a value that depends on the entire dataset. Such operations, for example, include finding the average value, the sum of the elements, greater or lesser. This approach was dealt with in the course on arrays.

reduce() is a bit more complicated than map() and filter() but in general retains the general approach with function passing. Let's implement a code that finds the total amount of money a group of people have. Here you can see the aggregation at once, we need to reduce the amount of money of all users to a single value:

const users = [
  { name: 'John', amount: 19 },
  { name: 'Richard', amount: 1 },
  { name: 'Antony', amount: 4 },
  { name: 'Alex', amount: 16 },
];

let sum = 0;
for (const user of users) {
  sum += user.amount;
}

console.log(sum); // => 40

The main difference between aggregation and mapping and filtering is that the result of aggregation can be any type of data - both primitive and compound, such as an array. In addition, aggregation often involves initialization with an initial value, which is commonly referred to as an accumulator. In the example above, it is executed on the line let sum = 0. Here the variable sum "accumulates" the result within itself.

Let's look at another example of aggregation - grouping user names by age:

const users = [
  { name: 'Peter', age: 4 },
  { name: 'John', age: 19 },
  { name: 'Antony', age: 4 },
  { name: 'Alex', age: 16 },
];

const usersByAge = {};
for (const { age, name } of users) {
  // Check if the age property has already been added to the resulting object or not
  if (!Object.hasOwn(usersByAge, age)) {
    usersByAge[age] = [];
  }
  usersByAge[age].push(name);
}

console.log(usersByAge);
// => { 4: [ 'Peter', 'Antony' ], 16: [ 'Alex' ], 19: [ 'John' ] }

In this example, the result of the aggregation is an object that has arrays written in its properties. This result is initially initiated by an empty object, and then gradually, at each iteration, is "filled" with the desired data. The value that accumulates the result of aggregation is commonly referred to as the word "accumulator". In the examples above, these are sum and usersByAge.

Let's implement the first example using reduce():

const users = [
  { name: 'John', amount: 19 },
  { name: 'Richard', amount: 1 },
  { name: 'Antony', amount: 4 },
  { name: 'Alex', amount: 16 },
];

const sum = users.reduce((acc, user) => {
  const newAcc = acc + user.amount;
  return newAcc;
}, 0);
// const sum = users.reduce((acc, user) => acc + user.amount, 0);

// Let's describe
// user: John, acc = 0, return value 0 + 19
// user: Richard, acc = 19, return value 19 + 1
// user: Antony, acc = 20, return value 20 + 4
// user: Alex, acc = 24, return value 24 + 16
console.log(sum); // => 40

The reduce() method takes two parameters as input, the handler function and the initial accumulator value. The same accumulator returns to the outside as the result of the entire operation.

The function passed to reduce() is the most important part and the key to understanding how the whole aggregation mechanism works. It takes two values as input. The first is the current accumulator value, the second is the current item being processed. The task of the function is to return the new accumulator value. reduce() does not analyze the contents of the accumulator in any way. All it does is pass it to each new call until the entire collection has been processed, and eventually return it to the outside. I emphasize that you should always return the accumulator, even if it has not changed.

The second example using reduce() looks like this:

// let's prepare the handler function
const cb = (acc, user) => {
  if (!Object.hasOwn(acc, user.age)) {
    acc[user.age] = [];
  }
  acc[user.age].push(user.name);
  return acc; // be sure to return it!
};

// Initial value is an empty object
const usersByAge = users.reduce(cb, {});

The code is pretty much the same, except the loop is gone and there is an accumulator return from the anonymous function.

reduce() Technically, you can work using only it, since it can replace both mapping and filtering. But you shouldn't do that. Aggregation controls the state (accumulator) explicitly. Such code is always more complicated and requires more action. So if it is possible to solve the problem by mapping() or filtering(), then this is what you should do.

How to think about reduce

Let's describe an algorithm that will help you correctly approach tasks that require reduce. Imagine that you have a list of courses with lessons within them and you need to count the number of all lessons. For example, this may be necessary to calculate the duration of the training program. Such problems occur regularly on Hexlet.

// A simplified structure so as not to overload
// In reality, there would be a lot of additional data about the course and the lessons
const courses = [
  {
    name: 'Arrays',
    lessons: [{ name: 'One' }, { name: 'Two' } ]
  },
  {
    name: 'Objects',
    lessons: [{ name: 'Lala' }, { name: 'One' }, { name: 'Two' } ]
  }
];

Here we see two courses with a total of 5 lessons. Now let's try to calculate this number programmatically. The first question to answer is, is this operation an aggregation? The answer is "Yes" because we are reducing the raw data to some calculable result. Then we see what operation results to. In our case, it is a number that is calculated as the sum of the lessons in each course. So the initial value of the accumulator will be 0 (you can refresh it here). Now a rough algorithm:

  1. Initialize the accumulated result with zero
  2. Going around the course collection one by one
    • Add the number of lessons in the current course to the accumulator

This algorithm will be identical in any variant of the solution, either through a loop or through a redirect:

// loop
let result = 0;
for (const course of courses) {
  result += course.lessons.length;
}
console.log(result); // => 5

// reduce
const result = courses.reduce((acc, course) => acc + course.lessons.length, 0);
console.log(result); // => 5

Implementation

Let's write our own myReduce(), function that works similarly to the reduce() array method:

const myReduce = (collection, callback, init) => {
  let acc = init; // Initializing acc
  for (const item of collection) {
    acc = callback(acc, item); // Replacing the old acc with a new one
  }
  return acc;
};

const users = [
  { name: 'Peter', age: 4 },
  { name: 'John', age: 19 },
  { name: 'Antony', age: 4 },
  { name: 'Alex', age: 16 },
];

const oldest = myReduce(
  users,
  (acc, user) => (user.age > acc.age ? user : acc),
  users[0],
);
console.log(oldest); // => { name: 'John', age: 19 }

Recommended materials

  1. reduce
  2. Lodash lib function

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.