Skip to main content

Aggregate Operations

An aggregate operation turns a whole collection into one value. Examples are the number of elements, their sum, the biggest element, or all elements combined into one result.

Arrays give you some of these directly. numbers.length counts the elements. Math.max() and Math.min() find the biggest and the smallest number; you hand them the elements with Math.max(...numbers). For a sum or an average there is no method: you combine the elements with reduce().

Be careful with empty arrays. Math.max() with no values returns -Infinity and Math.min() returns Infinity, so check numbers.length > 0 when the array might be empty.

Many languages have separate fold and reduce methods for combining elements one by one. Here both are the same method, reduce(), and the difference is only whether you pass an initial value. reduceRight() does the same walk from the last element to the first.


Basic Aggregates

The most common aggregates are count, minimum, maximum, sum and average.

  • numbers.length gives the count.
  • Math.max(...numbers) and Math.min(...numbers) give the biggest and the smallest number. They work for numbers only.
  • The sum is a reduce() that adds every element to a running total. The average is the sum divided by the count; the division keeps the decimal part, so 62 / 4 is 15.5.

Sometimes the smallest or biggest element should be picked by a rule, not by the value itself. reduce() does that too: in every step you keep the better of the two elements. This also works for words and objects, where Math.min() cannot help.

Sometimes every element must be turned into a number first and the results added up. That is one reduce() as well: add the computed value instead of the element itself.

Count, min, max, sum and average

For the numbers 6, 42, 10 and 4 the program prints the count with length, the biggest and the smallest with Math.max() and Math.min(), and the sum from a reduce(). The average is 62 / 4 = 15.5.

Prompt
Write a TypeScript program that takes the numbers 6, 42, 10 and 4 and prints the count, the largest, the smallest, the average and the sum, each on its own line with a label such as "Count:". Use length, Math.max, Math.min and a reduce() for the sum.
Output: TypeScript file and captured output
Count, min, max, sum and average

Min and max by a rule

The first reduce() keeps the element with the smaller remainder after dividing by 3. The remainders of 5, 42, 10 and 4 are 2, 0, 1 and 1, so 42 wins. The second one keeps the longer word and finds "three".

Prompt
Write a TypeScript program that prints the element with the smallest remainder after dividing by 3 among the numbers 5, 42, 10 and 4, using reduce() to keep the better element. Then print the longest of the words one, two, three, four in the same way.
Output: TypeScript file and captured output
Min and max by a rule

Sum of a function of each element

The callback adds a computed value to the total instead of the element itself. Doubling 5, 42, 10 and 4 gives 122. Halving them gives 30.5, because the division keeps the decimal part.

Prompt
Write a TypeScript program that takes the numbers 5, 42, 10 and 4 and uses reduce() with the initial value 0 to print the sum of each element times 2, and then the sum of each element divided by 2.
Output: TypeScript file and captured output
Sum of a function of each element

Fold and Reduce

Fold and reduce combine all elements into one value, step by step. They keep a running value called the accumulator. For each element they call a function with the accumulator and the element, and the function returns the new accumulator.

Both are the same method here. The difference is the second argument of reduce():

  • reduce(fn, initial) is a fold. It starts with the initial value you give it, so the function runs once for every element.
  • reduce(fn) is a reduce. It starts with the first element, so the function runs from the second element onward and the first element never goes through it.

This matters when the function changes the element. If it doubles every element, reduce(fn) leaves the first element as it is.

It matters for empty arrays too. reduce(fn, initial) simply returns the initial value, while reduce(fn) has nothing to start with and throws a TypeError.

To combine from the other end, use reduceRight(): the same method, walking from the last element to the first.

Reduce

reduce() with no initial value takes 5 as the start and adds 2, 10 and 4 to it, so the result is 21. The same call on an empty array throws, and the program prints the message it carries: Reduce of empty array with no initial value.

Prompt
Write a TypeScript program that adds up the numbers 5, 2, 10 and 4 with reduce() and no initial value, so the first element is the start, and prints the result. Then call the same reduce() on an empty array inside try/catch and print the error message.
Output: TypeScript file and captured output
Reduce

Fold

Here reduce() gets the initial value 0 and adds each element times 2. Every element is doubled: 10 + 4 + 20 + 8 = 42.

Prompt
Write a TypeScript program that uses reduce() with the initial value 0 on the numbers 5, 2, 10 and 4, adding each element times 2, and prints the result.
Output: TypeScript file and captured output
Fold

Fold versus Reduce

Both calls use the same function, sum plus element times 2. Without an initial value the result is 37, because the first element 5 is the start and is not doubled. With the initial value 0 the result is 42.

Prompt
Write a TypeScript program that runs the same function, sum plus element times 2, over the numbers 5, 2, 10 and 4 twice: once with reduce() and no initial value, once with the initial value 0. Print both results with the labels "Reduce:" and "Fold:" to show that without an initial value the first element is never doubled.
Output: TypeScript file and captured output
Fold versus Reduce

Fold from the right

reduceRight() walks the array from the last element to the first. Addition does not care about the order, so the result is again 42.

Prompt
Write a TypeScript program that uses reduceRight() with the initial value 0 on the numbers 5, 2, 10 and 4, adding each element times 2, and prints the result.
Output: TypeScript file and captured output
Fold from the right

Fold with Index

Sometimes the position of an element matters, not only its value.

The callback of reduce() gets the index as its third argument, after the accumulator and the element. A condition on that index then decides what to do with the element. reduceRight() hands over the same indexes; it only visits them from the end.

Fold with index

Only the elements at even indexes are added: index 0 holds 5 and index 2 holds 10, so the sum is 15.

Prompt
Write a TypeScript program that uses reduce() over the numbers 5, 2, 10 and 4 and adds up only the elements whose index is even, using the index argument of the callback. Print the sum.
Output: TypeScript file and captured output
Fold with index

Fold with index from the right

The same rule with reduceRight(). The callback sees index 3 first and index 0 last, and the result is still 15.

Prompt
Write a TypeScript program that uses reduceRight() over the numbers 5, 2, 10 and 4 and adds up only the elements whose index is even. Print the sum.
Output: TypeScript file and captured output
Fold with index from the right

Safe Reduce

reduce() without an initial value needs a first element to start with. On an empty array it throws a TypeError and stops the program.

One way out is an initial value. The other is to say "there is no result" honestly: check the length first and return undefined. The return type is then number | undefined, which makes the caller think about the empty case, and ?? puts a fallback value in place of undefined in one step.

Reduce that can return undefined

sumOrUndefined() returns 21 for the numbers 5, 2, 10 and 4, and undefined for an empty array. sumOrUndefined(empty) ?? 0 turns that undefined into 0.

Prompt
Write a TypeScript program with a sumOrUndefined function that returns number | undefined: it returns undefined for an empty array instead of throwing, and otherwise the sum from reduce(). Print the result for the numbers 5, 2, 10 and 4, then for an empty array, and then the empty case with ?? 0 as a fallback.
Output: TypeScript file and captured output
Reduce that can return undefined

Running Fold and Reduce

A running fold or reduce does not throw away the steps in between. It keeps the accumulator after every element, so the result is an array of all the running values.

There is no method for this. You keep a sum variable and push() it into a result array inside the loop. For a running reduce the result starts with the first element. For a running fold it starts with the initial value, so it holds one element more than the input.

Running reduce

The numbers 0 to 5 are added one by one. The result array holds every sum along the way and is printed on one line with join(' '): 0 1 3 6 10 15.

Prompt
Write a TypeScript program that takes the numbers 0, 1, 2, 3, 4 and 5 and builds an array of running sums: start with the first element and push the new sum after every further element. Print the array on one line with join(' ').
Output: TypeScript file and captured output
Running reduce

Running fold

The same with the initial value 10. The result begins with 10 itself and holds one element more than the input: 10 10 11 13 16 20 25.

Prompt
Write a TypeScript program that takes the numbers 0, 1, 2, 3, 4 and 5 and builds an array of running sums that starts with the initial value 10 and pushes the new sum after every element. Print the array on one line with join(' ').
Output: TypeScript file and captured output
Running fold