The Battle of the Array Titans: Lodash vs Vanilla - An Experiment
Tags: webdev, javascript, webperf
Published: 2020-07-28 04:41:45

## Contents
1. [Aim](#aim)
2. [Method](#method)
3. [Results](#results)
4. [Discussion](#discussion)
5. [Limitations](#limitations)
6. [Conclusion](#conclusion)

---

## Aim
To test the performance of common array methods between [lodash]( https://lodash.com/) and vanilla JavaScript.

---

## Method
We will use 2 different arrays in our experiment:
1. An array of integers
2. An array of objects


We will test the following methods:
* [filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
* [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
* [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)


We will run 3 tests, per array type, per method, and take the average time. Performance will be calculated using the native JavaScript [performance]( https://developer.mozilla.org/en-US/docs/Web/API/Performance) API.


**Data**
Our integer array will be a sorted array of 999,999 elements, with the values between 1 and 1,000,000.

Our object array will consist of 99,999 elements, each consisting of 4 properties; with string, integer and boolean property values.

The following script shows how this data will be gathered.


``` Javascript
// array of integers
const integerArr = [];
const intLen = 999999;
for (let i = 0; i < intLen; i++) {
  integerArr[i] = i + 1;
};

// array of objects
// age, gender and isProgrammer property values are
// decided at random
const peopleArr = [];
const peopLen = 99999;
for (let i = 0; i < peopLen; i++) {
  const obj = {
    name: 'Alex',
    age: Math.floor(Math.random() * 100),
    gender: Math.floor(Math.random() * 2) === 1 ? 'male' : 'female',
    isProgrammer: Math.floor(Math.random() * 2) === 1
  }

  peopleArr[i] = obj;
}

// export these arrays so we can use them in our test script
module.exports = {
  integerArr,
  peopleArr
};
```


**Test script**
The following script shows the methods used to perform the test with, along with the actual results gathering.

We import our arrays and define some helper functions to help perform the tests and print the results.
```JavaScript
const {performance} = require('perf_hooks');
const {reduce, map, filter} = require('lodash');
const {integerArr, peopleArr} = require('./data');

// helper function, which receives a function to perform,
// invokes that function, and returns the time taken to perform
function perfTest(test) {
  const start = performance.now();
  test();
  const end = performance.now();

  return (end - start).toFixed(3);
}

// a helper function which logs the time between 2 tests
function logTimes(time1, time2) {
  console.log(time1);
  console.log(time2);
}
```

Next, we create our functions for testing. Each testing function returns an object with 2 methods; one for testing the array of integers, the other for testing the array of objects.

``` Javascript
// each function returns an object of 2 methods
// 1. the test to perform on the integer array
// 2. the test to perform on the object array

// FILTERS
// 1. filter on even numbers
// 2. filter on females
function myFilters() {
  return {
    int: function(num) {
      return num % 2 === 0;
    },

    obj: function(person) {
      return person.gender === 'female';
    }
  }
}

// MAPS
// 1. multiply each value by 2
// 2. add a further property to object, isGodly, which is determined
// by whether the person is a programmer or not
function myMaps() {
  return {
    int: function(num) {
      return num * 2;
    },

    obj: function(person) {
      person.isGodly = person.isProgrammer;

      return person;
    }
  }
}

// REDUCE
// 1. return the sum of elements
// 2. return the sum of ages of people
function myReduces() {
  return {
    int: function(sum, num) {
      return sum += num;
    },

    obj: function(sum, person) {
      return sum += person.age;
    }
  }
}
```

Finally, we perform the tests and log the results!

```JavaScript
// invoke each test so we can easily access the individual tests
const filterTests = myFilters();
const mapTests = myMaps();
const reduceTests = myReduces();

/************ ROUND 1: FILTER *****************/
// lodash integer filter vs vanilla integer filter
const ft1 = perfTest(() => filter(integerArr, filterTests.int));
const ft2 = perfTest(() => integerArr.filter(filterTests.int));

// lodash object filter vs vanilla object filter
const ft3 = perfTest(() => filter(peopleArr, filterTests.obj));
const ft4 = perfTest(() => peopleArr.filter(filterTests.obj));


/************ ROUND 2: MAP ********************/
// lodash integer map vs vanilla integer map
const mt1 = perfTest(() => map(integerArr, mapTests.int));
const mt2 = perfTest(() => integerArr.map(mapTests.int));

// lodash object map vs vanilla object map
const mt3 = perfTest(() => map(peopleArr, mapTests.obj));
const mt4 = perfTest(() => peopleArr.map(mapTests.obj));


/************ ROUND 3: REDUCE *****************/
// lodash integer reduce vs vanilla integer reduce
const rt1 = perfTest(() => reduce(integerArr, reduceTests.int));
const rt2 = perfTest(() => integerArr.reduce(reduceTests.int));

// lodash object reduce vs vanilla object reduce
const rt3 = perfTest(() => reduce(peopleArr, reduceTests.obj));
const rt4 = perfTest(() => peopleArr.reduce(reduceTests.obj))

/************* RESULTS ************* */
console.log('MAP INTEGER');
logTimes(mt1, mt2);

console.log('MAP OBJECT');
logTimes(mt3, mt4);

console.log('FILTER INTEGER');
logTimes(ft1, ft2);

console.log('FILTER OBJECT');
logTimes(ft3, ft4);

console.log('REDUCE INTEGER');
logTimes(rt1, rt2);

console.log('REDUCE OBJECT');
logTimes(rt3, rt4);

```

---

## Results
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/banpmjstff7bn0jmky2a.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q6op678tftdtqilsvaf6.png)

### Filter
**Integer array**
Lodash: 26.670(ms)
Native: 26.689(ms)

**Object array**
Lodash: 5.525(ms)
Native: 2.885(ms)


### Map
**Integer array**
Lodash: 7.612(ms)
Native: 18.743(ms)

**Object array**
Lodash: 8.349(ms)
Native: 3.411(ms)


### Reduce
**Integer array**
Lodash: 5.028(ms)
Native: 21.073(ms)

**Object array**
Lodash: 6.392(ms)
Native: 2.482(ms)

---

## Discussion
**Filter**
* Filtering on an array of integers shows no significant difference, in terms of performance, between vanilla and lodash.

* Filtering on an array of objects shows that the vanilla filter method performs over twice as quick than the lodash variation.


**Map**
* Mapping on an array of integers shows the lodash variation been over twice as quick than the vanilla map.

* The vanilla implementation of map performed twice as quick than lodash, when mapping over an array of objects.


**Reduce**
* Reducing an array of integers shows that lodash performed over 4 times as quick than the vanilla variation.

* Reducing over an array of objects shows the vanilla reduce performing 3 times quicker than the lodash version.

---

## Limitations
This experiment is of course not perfect and it comes with some limitations that you should be wary of before deciding which you should consider using in your applications.

* The results are only applicable to the length of the data-sets tested.

* The results are only applicable to the array of data-types tested.

---

## Conclusion
* When looping over an array of integers, the lodash methods seem to perform a lot quicker than their vanilla counterparts.

* When looping over an array of objects, the vanilla methods out-perform the lodash variations.

-----

<span>Header photo by <a href="https://unsplash.com/@adlan7000?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Adlan</a> on <a href="https://unsplash.com/s/photos/battlefield?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>