Skip to content

Step 1 of 5 · Reading · ~4 min

Learn

Collections

Pipelines: Map, Filter, Reduce

Loops tell the computer HOW to iterate. filter, map and reduce say WHAT you want: keep these, transform them, collapse them to one answer. By the end of this lesson you can build a chain of the three, predict the type that comes out of each stage, and avoid the traps that make a pipeline return NaN, undefined or crash on an empty array.

The three stages

const fahrenheit: number[] = [14, 32, 50, 86];

fahrenheit.filter(f => f > 32);            // [50, 86]           keep where the callback is true
fahrenheit.map(f => (f - 32) * 5 / 9);     // [-10, 0, 10, 30]   transform every element
fahrenheit.reduce((acc, f) => acc + f, 0); // 182                fold into one value
  • filter returns a NEW array of the elements for which the callback returned true. The element type is unchanged and the array may be shorter. The original is untouched.
  • map returns a NEW array of whatever the callback returned: always the same length, possibly a different element type (nums.map(String) is string[]).
  • reduce threads an accumulator through the array. The callback is (accumulator, element) => newAccumulator, and the second argument to reduce is the accumulator's starting value. The result is a single value, which need not have the element's type.

Chaining

const warmTotal: number = fahrenheit
    .filter(f => f > 32)                 // number[]: [50, 86]
    .map(f => (f - 32) * 5 / 9)          // number[]: [10, 30]
    .reduce((acc, c) => acc + c, 0);     // number:   40

Each stage hands its result to the next, and TypeScript infers the type that flows out of every stage. If a map callback accidentally returned a string, the reduce below it would be flagged immediately.

Stages can also change the element type. This turns records into names, then into one string:

type Pet = { name: string; age: number };
const pets: Pet[] = [{ name: "Rex", age: 3 }, { name: "Mia", age: 9 }, { name: "Bo", age: 1 }];

const old: string = pets.filter(p => p.age > 2).map(p => p.name).join(" & ");  // "Rex & Mia"

Order matters. After map(p => p.name) the elements are strings, so a later filter(p => p.age > 2) would have nothing to read. Filter first, while the fields still exist.

Trap 1: reduce without an initial value

[].reduce((a, b) => a + b);      // TypeError: Reduce of empty array with no initial value
[].reduce((a, b) => a + b, 0);   // 0

With no initial value, reduce seeds the accumulator from the first element, and an EMPTY array has none, so it throws. Any pipeline whose filter might reject everything needs an explicit initial value.

Trap 2: forgetting to return

A concise arrow body returns automatically. A body in braces needs an explicit return:

nums.map(n => n * n);              // fine: implicit return
nums.map(n => { n * n; });         // BUG: returns undefined for every element
nums.map(n => { return n * n; });  // fine

The buggy version yields [undefined, undefined, ...], and later arithmetic on those turns into NaN.

Trap 3: passing a function with extra parameters

map calls its callback with (element, index, array). A function that takes a second parameter gets the index:

["10", "10", "10"].map(parseInt);          // [10, NaN, 2]: parseInt(x, index) reads the index as a radix
["10", "10", "10"].map(s => parseInt(s));  // [10, 10, 10]
["10", "10", "10"].map(Number);            // [10, 10, 10]: Number ignores extra arguments

Common mistakes

  • No initial value on reduce. It crashes the moment the filter rejects everything.
  • Braces without return. Every element becomes undefined.
  • Transforming before filtering. The fields you wanted to test are already gone.
  • > where the rule says "at least". The boundary value is silently dropped.

Your exercise

Summarise an order. The starter parses each input line into an object. You then use a pipeline over those objects to find the lines that qualify, list their names, and add up what they cost. The hidden tests include an order where nothing qualifies and a line sitting exactly on the boundary.

Up nextUnion and Intersection TypesType System Essentials

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…