All topics
ES6+beginner

Array.prototype Methods: flat, flatMap, includes

Three newer array methods that flatten nested arrays, combine mapping with flattening in one pass, and check for element presence without coercion quirks.

Three later additions that replace common workarounds.

flat() is like unpacking boxes containing smaller boxes onto one flat table. flatMap() does that unpacking while relabeling every item in one pass. includes() is a bouncer who recognizes a guest even with a smudged name badge (NaN), unlike an overly literal doorman (indexOf) who never matches a smudged badge.

Key Concepts

1
flat removes nesting.
    [1, [2, [3, [4]]]].flat();          // [1, 2, [3, [4]]]  -- depth 1
    [1, [2, [3, [4]]]].flat(2);         // [1, 2, 3, [4]]
    [1, [2, [3, [4]]]].flat(Infinity);  // [1, 2, 3, 4]
flat
2
    [1, , 3].flat();                    // [1, 3] -- removes holes too
3
It replaced [].concat(...arr) for one level, and a recursive function for more.
[].concat(...arr)
4
flatMap is map then flat, in one pass.
    orders.flatMap(o => o.items);            // all items, flattened
flatMap
5
    // filter and map together -- return [] to drop
    nums.flatMap(n => n > 0 ? [n * 2] : []);
6
That last pattern is the useful one: a single pass that both transforms and removes, where filter().map() makes two.
filter().map()
7
flatMap flattens exactly one level, always.
flatMap
8
includes against indexOf.
    arr.includes(x)           // clearer, and handles NaN
    arr.indexOf(x) !== -1
includesindexOf
9
    [NaN].includes(NaN);      // true
    [NaN].indexOf(NaN);       // -1
10
indexOf uses ===, and NaN === NaN is false. includes uses SameValueZero, which treats NaN as equal to itself.
indexOf===NaN === NaNincludesNaN
11
Both use strict comparison otherwise, so neither finds '1' when given 1.
'1'1
12
The related methods worth knowing alongside.
    at(-1)            the last element, without length - 1
    findLast()        search from the end
    findLastIndex()
    toSorted()        a sorted copy, no mutation
    toReversed()
    toSpliced()
    with(i, v)        a copy with one element replaced
13
The last four exist because sort, reverse and splice mutate, which is a repeated source of bugs where a function changes its caller's array.
sortreversesplice
14
    const sorted = items.sort();       // items is now sorted too
    const sorted = items.toSorted();   // items is untouched
15
at handles negative indices, which is worth having:
at
16
    arr[arr.length - 1]
    arr.at(-1)
17
What the interviewer is probing.1. "What does flat() do by default?" Probing: the depth. Stalls: "Flattens everything." Moves up: one level — pass a depth, or Infinity for fully nested.
18
2. "When is flatMap better than map then flat?" Probing: the combination. Stalls: "Never." Moves up: when each element maps to zero or more results, as it does both in one pass and expresses the intent directly.
19
3. "Why does includes find NaN when indexOf does not?" Probing: the comparison. Stalls: "They are the same." Moves up: includes uses SameValueZero, which treats NaN as equal to itself; indexOf uses strict equality, which does not.
20
4. "How do you remove an element conditionally?" Probing: immutability. Stalls: "splice." Moves up: filter, which returns a new array — splice mutates in place and is a common source of surprise in shared state.