I try to help

Basic JavaScript Array Functions

And one more complex one

Smells like JS

Approximate reading time: 6 minute(s)

Far too many times we don't realize the power of our platform. We have a layouting system that can handle almost anything and it's getting better and better with each passing browser version. We also have a very powerful scripting language, but most importantly we have the browser.

Modern browsers are incredibly powerful and featured. There are API's for practically everything. This post won't go into them in detail, those are great to experiment on your own time. What I do want to showcase is some very basic code smells that can be solved using simple Array functions.

Before we begin, a friendly warning: Using these functions may be a gateway to the world of functional programming!

Map

The map function is very powerful in that it returns a new array with the results of calling the provided function on every element in the array.

const array = [2, 3, 4];
const result = [];
for (let index = 0; index < array.length; index++) {
    result.push(array[index] * 3); 
}
// [6, 9, 12]

turns into:

const array = [2, 3, 4];
const result = array.map(element => element * 3);
// [6, 9, 12]

Filter

The filter function creates a new array which contains all the elements that pass a given condition:

const array = [2, 3, 4];
const result = [];
for (let index = 0; index < array.length; index++) {
    if (array[index] % 2 === 0) {
        result.push(array[index]); 
    }
}
// [2, 4]

turns into

const array = [2, 3, 4];
const result = array.filter( element => element % 2 === 0);
// [2, 4]

Reduce

Ah, the reduce function. The bane of every interview, the one that everything else is based on. This function executes a reducer function on each element, whilst keeping an accumulator and outputs that. The most common example is that of a sum of values:

const array = [1, 2, 3, 4];
const result = array.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
// 10

Personally I'm not a big fan of this example, mainly because this doesn't really showcase the power of the reduce function. Since we control the initial value of the accumulator we can do all sorts of interesting things.

const array = [1, 2, 3, 4];
const result = array.reduce((accumulator, currentValue) => {
  if (currentValue > 2) {
    accumulator.push(currentValue + 3);
  }
  return accumulator;
}, []);
// [6, 7]

If your code looks something like this:

const result = [];
const array = [1, 2, 3, 4];
array.forEach(value => {
  // some work which results in someValue
  result.push(someValue);
});

Then you can use a reduce instead!

An easy way to spot that you can use a reduce function is the first line:

const result = [];

This, generally, is a clear sign that you can use a reduce function, since you're initializing an accumulator to an empty array. Here's the above snippet converted to a reduce:

const array = [1, 2, 3, 4];
const result = array.reduce((accumulator, currentValue) => {
  // some work which results in someValue
  result.push(someValue);
}, []);

Some/Every

Sometimes you want to ensure that all values conform to a condition, or at least one value conforms to a condition. Let's check that we only have positive numbers:

let allPositive = true;
const array = [-1, 2, 3, 4];
for (let index = 0; index < array.length; index++) {
  if (array[index] < 0) {
    allPositive = false;
    break;
  }
}

We could use a forEach to do our checking!

let allPositive = true;
const array = [-1, 2, 3, 4];
array.forEach(value => {
  if (value < 0) {
    allPositive = false
  }
}

But our for-loop is more efficient because it exits early and doesn't go through all the values.

Let's try something else:

const array = [-1, 2, 3, 4];
const allPositive = array.every(value => value < 0);
const somePositive = array.some(value => value > 0);
// allPositive: false
// somePositive: true

Both some and every exit early so you don't have to worry about inefficiencies.

Bonus: flatMap

The newly minted flatMap is an interesting one. It's a map, followed by a flat. This may not seem very interesting at first, but it does have some potential.

Here's our reduce example, but implemented with flatMap:

const array = [1, 2, 3, 4];
const result = array.flatMap((value) => {
  if (value > 2) {
    return [value];
  }
  return [];
});
// [3, 4]

The MDN example is even more interesting, here it is:

// Let's say we want to remove all the negative numbers and split the odd numbers into an even number and a 1
const a = [5, 4, -3, 20, 17, -33, -4, 18]
//        |\  \  x   |  | \   x   x   |
//        [4,1, 4,   20, 16, 1,       18]

a.flatMap( (n) =>
  (n < 0) ?      [] :
  (n % 2 == 0) ? [n] :
                 [n-1, 1]
)

// expected output: [4, 1, 4, 20, 16, 1, 18]

Here we see that flatMap can be used in a very similar fashion to a reduce function. A more down to earth example is combining the list of comments of multiple users.

const users = [
  {
    name: 'J.S. Bach',
    comments: ['Do', 'Re', 'Mi', 'Fa']
  },
  {
    name: 'C.P.E. Bach',
    comments: ['Sol', 'La', 'Ti', 'Do']
  }
];
const result = users.flatMap(user => user.comments);
// ['Do', 'Re', 'Mi', 'Fa', 'Sol', 'La', 'Ti', 'Do'];

Performance

In general, all of these functions are slower than their simple counterparts. Their value isn't in their speed, it's in their meaning. People tend to make a big deal about being declarative and for good reason. We spend a lot of time reading code, far more than writing, so in this regard we benefit more in the long run from having more readable code.

For instance in this example, for me at least, flatMap is way slower, clocking in at around 2k ops/sec and reduce at around 4.6k ops/sec. However, in some instances, the syntax and expressivity offered by flatMap, may be desired.

The same benefit in expressivity is seen in forEach and map. Another benefit from using such functions is the fact that we can create chains of functions and reuse them in various parts of our application with ease.

Be aware though, that if you do something like:

const largeArray = [/*Large Array is large*/];
largeArray.map().map().reduce().filter()

You are creating a lot of intermediate arrays and this is highly inefficient. Consider placing the filter first, or combining them into one reduce so you only operate once over the data set. You might also want to read about transducers. Libraries like RxJS and other Observable implementations can be used as transducers. These functions allow our transformations to happen over each element of the initial array, in order, so that we only iterate over the array once.

Conclusions

If performance is what you want, you can't beat a while or a for loop. But for readability, consider using these functions to a larger degree.

Another side-effect of using these functions is that you are making the first steps into the world of functional programming. Tiny steps, but steps nonetheless.

Cheers!