In this post, I'll be sharing my favourite array methods! I'll dive into what they do, and when you might want to use them.
Before I begin, I think it's worth pointing out that everything shown here can be done with a standard for loop. These methods exists to help you write cleaner, more readable code that is ultimately more reusable.
All the methods mentioned below take a function as the first argument, which you specify. You can either write the function definition inline, or bring in an existing function from elsewhere in your codebase.
Generally speaking, the function you provide can take up to three arguments:
- The element in the iteration
- The index of the iteration (starting at 0)
- The array itself
Most of the time, you'll only need the first argument, but with more complex examples (which I'll get into) the other two become essential.
Map
Map will take your original array, and run each element in the array through your function, returning a new array of the same size.
The Basic Example
Let's perform some simple math on an array of numbers. For this, we'll only need a function taking one argument, the value for our current iteration.
const numbers = [1, 2, 3, 4, 5];
const numbersTimesTen = numbers.map(number => number * 10);
// numbers [1, 2, 3, 4, 5];
// numbersTimesTen [10, 20, 30, 40, 50]
For every number in our array, we are returning the result of the number multiplied by 10, resulting in a new array. Note that the original array is unchanged, a feature that makes map a powerful tool in functional programming.
Real World Use Case
Let's say that we have an array of ingredients, and we want to perform lookups in an object to get more information about them.
const nutrition = {
"1": {
name: "Olive Oil",
calsPer100g: 884,
carbsPer100g: 0,
protPer100g: 0,
fatPer100g: 100
},
"2": {
name: "Onion",
calsPer100g: 40,
carbsPer100g: 9,
protPer100g: 1.1,
fatPer100g: 0.1
}
};
const ingredients = [
{
id: "1",
grams: 5
},
{
id: "2",
grams: 100
}
];
const calories = ingredients.map(
ingredient => nutrition[ingredient.id].calsPer100g * (ingredient.grams / 100)
);
console.log(calories);
Here we're using the ingredient id to fetch the relevant nutritional data and use it in conjunction with the weight in grams to calculate the number of calories for the amount we are planning to use.
Filter
Filter, as the name suggests, can be used to filter out unwanted items from an array. You simply pass filter a function that returns either true or false - each element returning true (or at least a truthy value) will be returned in your new array.
The Basic Example
Let's filter our array to only show names beginning with 'A':
const names = ["Derek", "Norman", "Arnold", "Horace", "Archibald"];
const filtered = names.filter(name => name.startsWith("A"));
// filtered ['Arnold', 'Archibald']
Think of our function as a check to perform on each name. If the name satisfies our condition, it'll be returned in the filtered array.
Real World Use Case
We have an array of recipes, stating the name, the cuisine and whether or not the recipe is suitable for vegetarians. What we'll do here is filter our recipes to show only those where the cusine is Italian, and the recipe is suitable for vegetarians.
const recipes = [
{
name: "Spaghetti Bolognese",
cuisine: "Italian",
vegetarian: false
},
{
name: "Pie & Mash",
cuisine: "British",
vegetarian: false
},
{
name: "Souvlaki",
cuisine: "Greek",
vegetarian: false
},
{
name: "Margherita Pizza",
cuisine: "Italian",
vegetarian: true
}
];
const italianVegetarianRecipes = recipes.filter(
recipe => recipe.cuisine === "Italian" && recipe.vegetarian
);
console.log(italianVegetarianRecipes);
Reduce
Reduce is probably the most flexible of the array methods, and with that, it can be the most difficult to get your head around. I like to think of it as taking an array and making it into something smaller - in other words, reducing it down.
With this in mind, you could use reduce in place of map or filter if you really wanted to. Reduce can be used to create a string, number, Object or even another array.
While map and filter take a function as their sole argument, reduce also accepts an optional starting value argument. This is useful for making your code more readable as it's then obvious to the reader what reduce is being used to create.
The Basic Example
If you ever Google reduce, you'll often see an example with an array of numbers, where reduce is used to sum them up. I'll happily oblige with that example.
Here we have our array of numbers. Unlike map and filter, the first argument to your function is your accumulator - this is whatever you are building. Our accumulator is starting at 0, which we have used as the second argument after our reducer function - the start value.
The function simply modifies our accumulator to add on the value from the current iteration.
const numbers = [1, 5, 10, 15, 20];
const sum = numbers.reduce(
(accumulator, currentValue) => (accumulator += currentValue),
0
);
console.log(sum); // 51
Real World Example
This is one of my favourite ways to use reduce. Here we have an array of objects, and I want to create a new object grouping by a common key that appears in each of these objects.
With a large array, this technique becomes seriously powerful.
const recipes = [
{
recipe: "Fish & Chips",
cuisine: "British"
},
{
recipe: "Spaghetti Carbonara",
cuisine: "Italian"
},
{
recipe: "Pie & Mash",
cuisine: "British"
},
{
recipe: "Moussaka",
cuisine: "Greek"
},
{
recipe: "Duck l'orange",
cuisine: "French"
}
];
const groupedByCuisine = recipes.reduce((object, recipe) => {
const cuisine = recipe.cuisine;
if (!object[cuisine]) object[cuisine] = [];
object[cuisine].push(recipe);
return object;
}, {});
console.log(groupedByCuisine);
Let's break this down into steps.
- We are starting with an empty object. That's the second argument we are passing to the reduce method.
- We are constructing on object and iterating through recipes, so I've named the two parameters for our reducer function accordingly.
- We are assigning the cuisine value from our current iteration to a new variable, 'cuisine'.
- We are checking to see if a key exists for the cuisine in the object we are building. If it isn't there, we'll set it to equal an empty array.
- We push the current recipe into the array we have created.
- Finally, we return the object. This is necessary for our reducer to keep track of what we are building.
The last step wasn't necessary in the more Basic Example as we were making use of the implicit return available in arrow functions. I won't go into that here but there are plenty of resources to look that up.
Some
Some can be treated very much like filter - it takes a function that is checking for a condition. The difference is that it will return a boolean true or false. If any items in the array satisfy your condition, it will return true. Otherwise, it will return false.
The beauty of some is that it'll break out of the loop as soon as it finds an item satisfying your condition.
The Basic Example
Simply put, are any of the numbers in our array over 50?
const numbers = [4, 8, 12, 99, 41];
const hasNumbersOver50 = numbers.some(number => number > 50);
console.log(hasNumbersOver50); // true
This is a pretty simple method, so I think the Basic Example will do.
Every
Every can be used exactly like some, however as the name suggests, every item in the array must satisfy the condition specified in your function for the method to return true.
The Basic Example
Let's use the same example we had for some:
const numbers = [4, 8, 12, 99, 41];
const allNumbersOver50 = numbers.every(number => number > 50);
console.log(allNumbersOver50); // false
This returns false, because there are values in the array that are not greater than 50.
Find
Find, much like filter, some and every, takes a function checking for a condition. It'll return the first item in the array that satisfies your condition.
The Basic Example
A rather pointless exercise I accept, but demonstrates that it'll return the first item in the array matching the condition. Very useful when multiple items could satisfy the condition and you only need one.
const fruits = ["Apple", "Orange", "Banana", "Kiwi", "Orange"];
const findOrange = fruits.find(fruit => fruit === "Orange");
console.log(findOrange); // 'Orange'
findIndex
findIndex can be used in a similar way to find, but it'll return the index of the item satisfying the condition in the array, rather than the item itself.
The Basic Example
const fruits = ["Apple", "Orange", "Banana", "Kiwi", "Orange"];
const orangeIndex = fruits.findIndex(fruit => fruit === "Orange");
console.log(orangeIndex); // 1
Real World Use Case
I recently used this in conjunction with filter to build an array of objects ensuring a particular key was not repeated. Let me demonstrate:
const recipes = [
{
recipe: "Fish & Chips",
cuisine: "British"
},
{
recipe: "Spaghetti Carbonara",
cuisine: "Italian"
},
{
recipe: "Pie & Mash",
cuisine: "British"
},
{
recipe: "Moussaka",
cuisine: "Greek"
},
{
recipe: "Duck l'orange",
cuisine: "French"
}
];
const oneRecipePerCuisine = recipes.filter((item, index, array) => {
return index === array.findIndex(recipe => recipe.cuisine === item.cuisine);
});
console.log(oneRecipePerCuisine);
With this, we've filtered out 'Pie & Mash' because the it's not the first recipe under British cuisine.
Sort
The sort method is somewhat different from the other methods mentioned because it mutates the original array. What this means is that when you call sort on an array, the order of the array itself is updated. This can lead to unexpected behaviour if you aren't aware of this.
A neat little way around this is to make a copy of the array using:
Array.from(originalArray).sort(...);
Sort will take a comparison function as the sole argument. The comparison function will have two parameters, and it's common practice to name these a and b - they refer to the two items you are comparing to determine how the array is sorted.
The Basic Example
At the most basic level, sort can be used to order arrays alphabetically.
const names = [
"Leonardo",
"Rafael",
"Donatello",
"Michelangelo",
"April",
"Splinter"
];
names.sort((a,b) => a > b ? 1 : -1);
console.log(names);
We're using a ternary operator to establish if a or b is first alphabetically. If a is greater than b, we return 1, otherwise we return -1. The reason for this is that if the value returned by the compare function is greater than 0, b will be used first in our sorted array. If the return value is less than 0, a will be first.
Real World Use Case
This can get seriously complex with an array of objects where you might want to sort on more than one thing. Let's sort our recipes by cuisine, then recipe name.
const recipes = [
{
recipe: "Fish & Chips",
cuisine: "British"
},
{
recipe: "Spaghetti Carbonara",
cuisine: "Italian"
},
{
recipe: "Pie & Mash",
cuisine: "British"
},
{
recipe: "Moussaka",
cuisine: "Greek"
},
{
recipe: "Duck l'orange",
cuisine: "French"
}
];
recipes.sort((a,b) => {
if(a.cuisine > b.cuisine) return 1;
if(a.cuisine < b.cuisine) return -1;
if(a.recipe > b.recipe) return 1;
if(a.recipe < b.recipe) return -1;
});
console.log(recipes);
forEach
I've saved forEach for last because it behaves differently to the other methods I've gone into. This is a void method, which means it isn't going to return anything, but it does allow us to do something with each of the items in an array.
The Basic Example
const names = ["John", "Paul", "Ringo", "George"];
names.forEach(name => console.log(name));
/*
"John"
"Paul"
"Ringo"
"George"
Real World Use Case
I find forEach particularly useful when you want to make additions to an array of objects. In this case, often mutatability is not a concern because you aren't changing existing data, but adding to it. Let's take our recipes again, and for each recipe, specify an image path constructed using the recipe name and cuisine.
const recipes = [
{
recipe: "Fish & Chips",
cuisine: "British"
},
{
recipe: "Spaghetti Carbonara",
cuisine: "Italian"
},
{
recipe: "Pie & Mash",
cuisine: "British"
},
{
recipe: "Moussaka",
cuisine: "Greek"
},
{
recipe: "Duck l'orange",
cuisine: "French"
}
];
recipes.forEach(
recipe =>
(recipe.imagePath = `img/recipes/${recipe.cuisine}/${recipe.recipe.replace(
/[^a-z]/gi,
""
)}.jpg`.toLowerCase())
);
console.log(recipes);
Chaining
It's worth pointing out that all of the methods mentioned here can be chained, which means you can use method upon method like so:
array.map(mapFunc).filter(filterFunc).sort(sortFunc);
Which can hugely reduce the amount of code you need to write to get your data in the form you require!
Final Thoughts
As I mentioned at the beginning, everything we have done here could be achieved with standard for loops. However, this would require code far more verbose, arguably less readable, certainly less maintainable.
These methods have helped me with data manipulation and I call on some of them (looking at you, map, filter and reduce) on a daily basis. I find them ever so powerful, hopefully this article allows you to harness that power for your own needs!