Demystifying Array Methods
Tags: javascript, programming, beginners, tutorial
Published: 2023-07-15 20:07:57

Someway, somehow, people think the `for` loops are easier to grasp and understand… I’m here to change that.

I’ll be using Javascript, but most languages implement the methods in one way or another, sometimes with some variance in naming.

## Basics

Most methods, with exceptions, have the same signature.

Let’s compare with the `for` loops:

```js
const array = ['a', 'b', 'c'];

// the classic `for` loop you can have better
// control over the indexed you're using
for (let index = 0; index < array.length; index++) {
  const element = array[index];
  console.log(element); // a, b, c
}

// the `for in` gives the index
for (const index in array) {
  const element = array[index];
  console.log(element); // a, b, c
}

// the `for of` gives the element
for (const element of array) {
  console.log(element); // a, b, c
}
```

- element - usually this is all you need, really
- index - there are uses to using the index
- array - when chaining methods, this allows you to use the current values in the step you’re in.

### Two things to remember are:

- Use the best method for what you’re doing

If you don’t need to return anything, using a `map` that returns will make me think you forgot to return something. 

Likewise, there are many roundabout ways that the current state of the language has better ways of doing, like using `findIndex !== -1` when a `some` will do the same.

- You can chain methods

As long as what you return is another array, then you can just do something and pass it to the next step. 

Sometimes this makes sense, but sometimes, you may want to assign them to descriptive variables.

## forEach

“For each” element in the array, it does something and doesn’t return anything.

```js
['a', 'b', 'c'].forEach((element) => {
  // console.log doesn't return anything
  // so it's perfect for the forEach
  console.log(element); // a, b, c
})
```

You use it when you just need to do something and not return anything: logs, toasts…

It’s easy to understand and use, but since it doesn’t return anything, there aren’t really many uses. (there are better methods for whatever you’re thinking)

## map

The poster child of the array methods!

For each element in the array, it returns a value.

(note: even if you don’t return, it returns `undefined`)

```js
// not adding {} in the arrow function, means it returns implicitly
[1, 2, 3].map(element => element * 2); // result array is [2, 4, 6]

[1, 2, 3].map(element => {
  if(element % 2 !== 0) { // remainder of the division by 2 not equal to zero 
    return element * 2;
  }
  // no "else" returns
}) // resulting array [2, undefined, 6]
```

Since it returns an array of the same size, it can be used for multiple things and makes chaining easy. 

## reduce

The bane of people learning the methods… but it’s not really that complicated.

For each element in the array, it returns “one” thing (that can be anything). 

Optionally, you can have a starting value.

```js
// classical use case
[1, 2, 3].reduce((accumulator, element) => accumulator + element, 0); // returns 6

// classical mistake nr 1: not returning anything
[1, 2, 3].reduce((accumulator, element) => {
  console.log(accumulator); // 0 (initial value), undefined, undefined (undefined because you didn't return)
  console.log(element); // 1, 2, 3
  accumulator + element; // this would be 1 (0 + 1), then NaN, NaN (number + undefined/NaN)
  // always remember to return something, even if only the accumulator
}, 0);

// (possible) mistake: not having an initial value
// no initial value means the first element is the initial value
[1, 2, 3].reduce((accumulator, element)=> accumulator + element); // retuns 6
[].reduce((accumulator, element)=> accumulator + element); // throws because empty array with no initial value

// things start to be different for more complex use cases
[1, 2, 3].reduce((accumulator, element)=>{
  if (element % 2 === 0) {
    accumulator.even.push(element);
  } else {
    accumulator.odd.push(element);
  }

  return accumulator;
// below I'm using JSDoc to type the initial value
// because using JS doesn't mean not using types ;]
}, /** @type {{ even: number[], odd: number[] }} */ ({ even: [], odd: []}));
// this returns: { even: [ 2 ], odd: [ 1, 3 ] }
// Not using an initial value will break this in multiple ways.

// this is another way to write the same thing
[1, 2, 3].reduce((accumulator, element)=>{
  if (element % 2 === 0) {
    return {
      ...accumulator,
      even: accumulator.even.concat(element),
    }
  }

  return {
    ...accumulator,
    odd: accumulator.odd.concat(element),
  }
}, /** @type {{ even: number[], odd: number[] }} */({ even: [], odd: []}));
// the important thing to remember is to always return something

// the "one" thing it returns can be anything:
[1, 2, 3].reduce((accumulator, element) => {
  if (element % 2 === 0) {
    accumulator[1] += element;
  } else {
    accumulator[0] += element;
  }
  return accumulator;
}, /** @type {[number, number]} [*/([0, 0]));
// this returns [4, 2]
// and would again have multiple problems without an initial value
```

If you were to instantiate one or more variables outside, then use a `for` loop or even a `forEach` and mutate it, then it’s a use case for `reduce`.

It always return “one” thing, and as you can see it can be anything: number, string, array, object…

While, you can do basically anything with `reduce`, check if there isn’t any other method that cover the case better.

There’s also a `reduceRight` that is the same thing, but starts from the last element.

## filter

For each element in the array, returns an array that can be of a smaller size.

While `reduce` returns “one” thing, `filter` always return an array that can be from empty to the same size of the input.

```js
[1, 2, 3].filter(element => element % 2 !== 0); // returns [1, 3]
[1, 2, 3].filter(element => element % 5 === 0); // return [0]

// one common use case: remove falsy values
[1, 2, 3].map(element => {
  if (element % 2 !== 0) {
    return element;
  }
}) // at this point you would have: [1, undefined, 3]
  .filter(Boolean); // after this return [1, 3]
```

If you want to remove values from an array, `filter` is the method you want.

And for those who didn’t know: `filter(Boolean)` is an elegant way of removing falsy values.

You should be careful, that it removes falsy values like `''` (empty string) and `0` (zero).

## flat and flatMap

Flatting an array is not something you use every day, but is certainly useful in many cases. Basically, to `flat` is to remove dimensions from an array.

If you have a 2x2 matrix: `[[1, 1], [1, 1]]` and you flat it, you end up with a simple 4 items array: `[1, 1, 1, 1]`.

```js
[1, 2, 3].map(element => {
  return [[[element]]]; // sometimes you return values/tuples
  // depending on what you really wanted to return, you just need to flat it
}) // here would return [[[[1]]],[[[2]]],[[[3]]]]
  .flat(1) // returns [[[1]],[[2]],[[3]]]
  .flat(2); // returns [1, 2, 3]

[1, 2, 3].map(element => {
  return [[[element]]];
}) // here would return [[[[1]]],[[[2]]],[[[3]]]]
  .flat(Infinity); // returns [1, 2, 3]
  // flatting to infinity means it will remove all dimensions
```

You call `flat` from an array and it takes a number that is how many dimensions you’re stripping from it. If you want a simple array as result no matter how many dimensions the array has, then use the `flat(Infinity)` as it will do just that.

```js
[1, 2, 3].flatMap(element => {
  return [element * 2]; // returns [2], [4], [6]
}); // returns [2, 4, 6] because it flattened

[1, 2, 3].flatMap(element => {
  if (element % 2 === 0) {
    return element * 2; // return 4
  }
  // without returning anything, it would result in [undefined, 4, undefined]
  // while flatMap doesn't remove falsy values
  // a flattened empty array disappear
  return [];
}); // returns [4]
```

`flatMap` is basically a `map` followed by `flat(1)`.

TIL: you can use `flatMap` and return `[]` for falsy values. It will return just the values

## some and every

Those are fun ones that, after learning about you will find use cases everywhere!

Usually, the use case is either: `filter(/* for something */).length /* (not) equal to something */` or `Boolean(find(/* something (not) equal */))` . 

For each element in the array, `some` returns true if at least one passes the predicate, `every` if all passes it.

```js
[1, 2, 3].some(element => element % 2 === 0); // true
[1, 2, 3].every(element => element % 2 === 0); // false
```

One cool thing is that they both return early, `some` on the first `truthy` value returned and `every` on the first `falsy` value returned. 

When all you need is a boolean from if there is or not something, then use either `some` or `every`.

## find and findIndex

`find` will return the first element that passes the predicate or `undefined` otherwise, `findIndex` will return the index of the element or `-1` otherwise.

Both start searching from index `0`, but if you want the last of those, then use the “last” variations.

```js
// returns the element:
[1, 2, 3].find(element => element % 2 !== 0); // 1
[1, 2, 3].findLast(element => element % 2 !== 0); // 3

// return the index:
[1, 2, 3].findIndex(element => element % 2 !== 0); // 0
[1, 2, 3].findLastIndex(element => element % 2 !== 0); // 2
```

## includes

Sometimes you just want to check if a value exists inside an array, this checks for deep equality.

```js
[1, 2, 3].includes(1); // true
[1, 2, 3].includes('1'); // false
```

Likely, we can include here `indexOf` and `lastIndexOf` both checking for a value, but returning the index or `-1` otherwise.

```js
[1, 0, 1].indexOf(1); // 0
[1, 0, 1].indexOf('1'); // -1

[1, 0, 1].lastIndexOf(1); // 2
[1, 0, 1].lastIndexOf('1'); // -1
```

The use case for `includes` is usually searching an array of primitive values (string, number, symbols…), meanwhile `some` and `every` are used for things more complex than if the value is there or not or for objects.

## Last remarks

The [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) are as good as they come. You might not have known all of those existed, but once you do they are pretty straightforward. 

Then again, some people might know the methods exist, but not how to apply them. So, if you want, send examples you don’t know how to apply the methods and I can refactor options.