Danieo's Blog

WebDev, programming and my thoughts

JavaScript array methods 2/3 - iterating arrays

As we know, arrays are collections of elements. JavaScript arrays have something named iteration methods - these methods operate on every element of the collection and can help us with creating new arrays based on individual entries of our original array or just simply do something with every single element. In this part of our JavaScript array methods series, we are going to cover them in deep.


Table of contents

Looping through an array

Looping (or iterating) through an array in most languages is commonly done using a for-loop. JavaScript is not different.

const images = [
  "https://image-cdn.com/my-image-1.jpeg",
  "https://image-cdn.com/my-image-2.jpeg",
  "https://image-cdn.com/my-image-3.jpeg",
];

for (let i = 0; i < images.length; ++i) {
  console.dir(images[i]);
}

This code is going to output every single URL in the images array. As you see, our iteration is working, nothing special. It may look familiar to you if you worked with languages other than JavaScript.

However, it's not the only way to loop through our array. The Array prototype has implemented a forEach method, which calls a callback on every element of an array.

const images = [
  "https://image-cdn.com/my-image-1.jpeg",
  "https://image-cdn.com/my-image-2.jpeg",
  "https://image-cdn.com/my-image-3.jpeg",
];

images.forEach((image) => console.dir(image));
NOTE. You can access the index of the current element by passing a second argument to the function inside forEach.

The result is the same - we've printed every element of this array. Although there is a difference between a classic for loop and a forEach - performance. forEach may be more convenient, but it's slower, so when you're dealing with big arrays you shouldn't use it.

Benchmark results - for with an iterator is more performant than forEach
Looping through a 10 000 elements array using three different methods.

In these benchmark results, you can see that there is a third method of iteration, which is a bit faster than forEach and more friendly than a classic for loop - I'm talking about for...of. It was introduced after for and forEach and works...

const images = [
  "https://image-cdn.com/my-image-1.jpeg",
  "https://image-cdn.com/my-image-2.jpeg",
  "https://image-cdn.com/my-image-3.jpeg",
];

for (const image of images) {
  console.dir(image);
}

...the same way - I mean, it produces the same result. Being slightly more performant than forEach it's a better choice in most cases. Furthermore, opposite to forEach, it can be controlled with statements like break, but this article is not about loops, so we're going to stop talking about for...of.

Modify all elements of an array - mapping arrays

Sometimes you'll need to transform every single element of your array and create a new array with these elements. In this case, map is the remedy. It simply runs a callback on every element and then creates a new array from the results.

const names = ["dave", "emma", "alan", "simon", "stacy"];
const capitalizedNames = names.map((name) => {
  return name[0].toUpperCase() + name.slice(1);
});

console.dir(capitalizedNames); // Output: ["Dave", "Emma", "Alan", "Simon", "Stacy"]

This example is going to capitalize the first letter of every word in the array and return a new array consisting of capitalized words.

NOTE. You can access the index of the current element by passing a second argument to the function inside map.

With using map comes one thing that you need to remember - the resulting array is the same length as the original array and every missing element is just changed to undefined. It can occur in a case like this:

const array = ["1", "6", "17", "boo!", "32"];

const numbers = array.map((x) => {
  const n = +x; // It's just a short way to cast a string into number
  if (!isNaN(n)) {
    return n;
  }
});

console.dir(numbers); // Output: [1, 6, 17, undefined, 32]

In this example, we are converting an array of numeric strings to an array of numbers. There is only one problem, when conversion fails we get a NaN, and our statements under the condition are never called so this iteration never returns a value, in this case, map is going to return an undefined for that element.

Mapping and flattening?

Now, as we already covered map, let's talk about flatMap, which works like map followed by flat. Let's assume we have a text as an array of sentences and we want to tokenize it.

const text = [
  "I've gotta go. You'll find out in thirty years.",
  "That's a great idea. I'd love to park.",
  "What the hell is a gigawatt? Lorraine, are you up there?",
];

const sentenceToken = text.map((sentence) => sentence.split(" ")).flat();
console.dir(sentenceToken); // Output: [ "I've", "gotta", "go.", "You'll", "find", "out", "in", "thirty", "years.", "That's", … ]
NOTE. It's not a proper way to tokenize strings. It's a very simplified example to show how flatMap works.

We map our text array and create an array of arrays containing single word tokens, then we flatten that array to get a one-dimensional array with all tokens. Simple, right? But do you know that we can do it better using flatMap?

const text = [
  "I've gotta go. You'll find out in thirty years.",
  "That's a great idea. I'd love to park.",
  "What the hell is a gigawatt? Lorraine, are you up there?",
];

const sentenceToken = text.flatMap((sentence) => sentence.split(" "));
console.dir(sentenceToken); // Output: [ "I've", "gotta", "go.", "You'll", "find", "out", "in", "thirty", "years.", "That's", … ]

It produces the same result, is a bit shorter, and is also slightly more performant.

Benchmark result - flatMap is more performant than map + flat

The choice should be obvious.

Reducing arrays

Reducing is a process where an array is reduced to a single value, it is achieved by calling a reducer function on every element. A reducer function can take four arguments:

  • Accumulator - it contains a value that is passed to every iteration, and after the final iteration, it becomes the value returned by reduce.
  • Current value - as the name says, it's the value of the current element.
  • Current index - an array index of the current iteration.
  • Source array - the array on which reduce is called.

Now some of you may wonder "ok, but where can I use this method?". Let's assume we have an array of numbers and we want to count the sum of its elements. It can be done using a for and adding every element of this array to a variable, but also it can be done using reduce.

Counting sum of array elements

const numbers = [77, 94, 668, 371, 2, 194, 54, 674, 7, 213, 26];
const sum = numbers.reduce((acc, value) => acc + value);
console.dir(sum); // Output: 2380

Reduce can also be used to find the minimal and maximal value in an array.

Finding minimum and maximum in an array

const numbers = [77, 94, 668, 371, 2, 194, 54, 674, 7, 213, 26];
const min = numbers.reduce((acc, value) => acc < value ? acc : value);
console.dir(min); // Output: 2
const numbers = [77, 94, 668, 371, 2, 194, 54, 674, 7, 213, 26];
const max = numbers.reduce((acc, value) => acc > value ? acc : value);
console.dir(max); // Output: 674

But hey! JavaScript has methods like min and max in its Math object, can't we just use them? Of course, we can! Although, surprisingly, using reduce is faster. On a 10 000 elements array the result is as follows:

Benchmark results - Finding arrays minimal value using reduce is faster than Math.min

Let's check it also on a smaller array (with 10 elements).

Benchmark results - Finding arrays minimal value using reduce is faster than Math.min

Grouping objects in an array

Another very useful case for reduce is grouping objects in an array by their properties. Let's take a look at this example:

const animals = [
  { name: "Dog", group: "mammals" },
  { name: "Eagle", group: "birds" },
  { name: "Tiger", group: "mammals" },
  { name: "Dolphin", group: "mammals" },
  { name: "Frog", group: "amphibians" },
  { name: "Parrot", group: "birds" },
];

const groupsSchema = {
  mammals: [],
  birds: [],
  amphibians: [],
};

const groups = animals.reduce((acc, value) => {
  acc[value.group].push(value);
  return acc;
}, groupsSchema);

console.dir(groups);

In this example we got an array of animals, every animal has its name and the group it belongs to. Using reduce we group them into separate arrays based on the value of group. If you haven't noticed - we can pass an initial value for our accumulator by passing a second argument to reduce.

Reducing backward?

reduce is going to iterate from the lowest index to the highest (from start to end). However, sometimes we may need to reduce an array backward - in that case, we can use reduceRight. It works identically to reduce, only the iteration starts from the highest index and goes to the lowest index.

const array = [[1, 2], [3, 4], [5, 6]];
const result1 = array.reduce((acc, value) => acc.concat(value));
const result2 = array.reduceRight((acc, value) => acc.concat(value));
console.dir(result1); // Output: [1, 2, 3, 4, 5, 6]
console.dir(result2); // Output: [5, 6, 3, 4, 1, 2]

Check if every element fulfils your condition

To check if all of the elements of an arrays are meeting our condition we can use every. This method runs a test on every element. If everything will pass, then it returns true - if not it returns false.

const positives = [1, 56, 17, 592, -5, 9];
const isEveryPositive = positives.every((value) => value > 0);
console.dir(isEveryPositive); // Output: false
const positives = [1, 56, 17, 592, 5, 9];
const isEveryPositive = positives.every((value) => value > 0);
console.dir(isEveryPositive); // Output: true
NOTE. The test function also accepts a second argument after value, it's the index of the current element.

Check if any element fulfils your condition

When you want to check if one or more element pass your test, you can use some. It's similar to every, but it expects only some values to pass the test.

const positives = ["Hammer", "Screwdriver", null, "Wrench"];
const isSomeNull = positives.some((value) => value === null);
console.dir(isSomeNull); // Output: true
const positives = ["Hammer", "Screwdriver", "Pliers", "Wrench"];
const isSomeNull = positives.some((value) => value === null);
console.dir(isSomeNull); // Output: false
NOTE. Similar to every you can also access the index of the current element by passing a second argument to the test function.

Filtering arrays

Removing elements that don't fulfill our conditions can be pretty handy. filter creates a new array consisting of elements that pass our test.

const numbers = [456, 1837, 123, 416, 12, 312, 7];
const filtered = numbers.filter((value) => value >= 100);
console.dir(filtered); // Output: [456, 1837, 123, 416, 312]

Removing duplicates from an array

const pets = ["Dog", "Cat", "Hamster", "Dog", "Canary"];
const filtered = pets.filter((value, index, array) => array.indexOf(value) === index);
console.dir(filtered); // Output: ["Dog", "Cat", "Hamster", "Canary"]
NOTE. this method is not performant on big arrays. With bigger arrays consider using the ES6 way to create unique arrays.
const pets = ["Dog", "Cat", "Hamster", "Dog", "Canary"];
const filtered = [...new Set(pets)];
console.dir(filtered); // Output: ["Dog", "Cat", "Hamster", "Canary"]

This will create a Set from our array and then convert it back to a classic array. Sets are collections like array but they have a unique constraint - they don't accept duplicates and every value is unique. Below is a test comparing these methods on a 1000 elements array.

Benchmark result - creating a set of a big array is more performant than filtering duplicates

and on a small 20 elements array.

Benchmark result - filter is more performant on small arrays than creating a Set

And that's all! We are almost at the end of this short series! The last part is going to cover searching in arrays. For now, take care! If you like my work consider signing up for my newsletter.

Rest of the series

Liked this article? Want to know about future content? Join my newsletter!