Data manipulation essentially means adjusting data to make it easier to read, understand and use. Whether you’re rendering UI, fetching data from an API, or reading from files, you will have to manipulate data in all sorts of JavaScript applications.

If you’ve worked enough on JavaScript projects, you’ll notice that it’s very common to see data in the form of arrays. Surely there are tons of resources covering JavaScript arrays out there, and anyone reading this article probably knows how to perform basic operations on arrays. Nevertheless, the main purpose of this article is to introduce you to different JavaScript array methods and practices that can be utilized to achieve simpler and cleaner code.

Starting with the basics

In this section, I will briefly mention some of the common static and instance array methods used to create, access, or mutate arrays. The content of this section might sound very basic or high-level for most readers, yet, it is important to cover it before diving deeper into more advanced topics.

Static methods

These methods are used to create new arrays or convert existing iterable and array-like objects to arrays.

// New array
Array.of("🍏", "🍌", "🍒"); // ["🍏", "🍌", "🍒"]
Array(3).fill("⭐️"); // ["⭐️", "⭐️", "⭐️"]

// From array-like object
Array.from("hello"); // ["h", "e", "l", "l", "o"]

// From iterable
Array.from([1, 2, 3], (x) => x * 2); // [2, 4, 6]

// From object
const person = { name: "John", age: "23" };

Object.keys(person); // ["name", "age"]
Object.values(person); // ["John", "23"]
Object.entries(person); // [["name", "John"], ["age", "23"]];


Mutation methods

The following methods are instance methods or prototypal methods. They are called on a specific array instance to apply mutations.


const fruits = ["🍏", "🍌", "🍒", "🍑", "🥑"];

// Inserts element at the end
fruits.push("🥭"); // 6
console.log(fruits); // ["🍏", "🍌", "🍒", "🍑", "🥑", "🥭"]

// Removes last element
fruits.pop(); // "🥭"
console.log(fruits); // ["🍏", "🍌", "🍒", "🍑", "🥑"]

// Inserts element at the start
fruits.unshift("🍉"); // 6
console.log(fruits); // ["🍉", "🍏", "🍌", "🍒", "🍑", "🥑"]

// Removes first element
fruits.shift(); // "🍉"
console.log(fruits); // ["🍏", "🍌", "🍒", "🍑", "🥑"]


Other instance methods

These are other pure instance methods that won’t mutate the array.


const tags = ["js", "react", "node", "js"];

tags.join(", "); // "js, react, node, js"
tags.indexOf("react"); // 1
tags.lastIndexOf("js"); // 3
tags.reverse(); // ["js", "node", "react", "js"]

// String to array
"29-10-2021".split("-"); // ["29", "10", "2021"]


Questioning traditional loops

When thinking about arrays, iteration is usually the first word that pops in mind. We iterate over arrays to apply the same logic for each value. At this point, it might seem that anything we want to apply to array members can be achieved using a traditional loop with all its variants (i.e for, for-of, and .forEach() loops).


Here are some examples where using traditional loops is perfectly fine:

const fruits = ["🍏", "🍌", "🍒", "🍑", "🥑"]; // yes, emoji arrays are cool

// ✅ A simple loop to print elements
for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);
}

// ✅✅ A better implementation
// implicit breaking condition but no index variable
for (fruit of fruits) {
  console.log(fruit);
}

// ✅✅✅ An even better implementation
// implicit breaking condition with optional access to index
fruits.forEach((fruit, _index) => {
  console.log(fruit);
});

// While loops are not included for brevity

But what if we need to do more than that, maybe modify or filter array elements? We can for sure use traditional loops, but can’t we do any better?


In the following section, I will present better alternatives to traditional loops in which each alternative will be compared to the .forEach() method for simplicity.

For the following examples, we will use this data set as reference:


const cart = [ { itemId: "szXwXmq9i0HpHS1_OlxxN", dateAdded: "06/14/2020 4:41:48 PM UTC", isItemAvailable: true, quantity: 2, price: 10.12, }, { itemId: "hKhoBgnD0cccymkGlGHaZ", dateAdded: "06/14/2020 4:51:50 PM UTC", isItemAvailable: true, quantity: 3, price: 15.01, }, { itemId: "EvxqG2f4Edd55DpRdcJ8y", dateAdded: "06/14/2020 4:56:01 PM UTC", isItemAvailable: true, quantity: 1, price: 24.98, }, { itemId: "tJONu8dVSWWTZbbElvZBP", dateAdded: "06/14/2020 5:00:20 PM UTC", isItemAvailable: false, quantity: 1, price: 35.6, }, ];


Replacing traditional loops


.map()

As the name suggests, Array.protoype.map() maps the elements of the current array to a new array after applying some logic on each element. Let’s say you’ve received the previous cart data which includes UTC timestamps and prices (as numbers), and that you need to localize your data based on the user’s browser settings:


const localizeDate = (utcStr = "") =>
  new Date(Date.parse(utcStr)).toLocaleString();

const localizePrice = (price = 0) =>
  price.toLocaleString("en-US", { style: "currency", currency: "USD" });

// Don't ❌
let localizedCart = [];
cart.forEach(({ dateAdded, price, ...item }) => {
  const localizedItem = {
    ...item,
    dateAdded: localizeDate(dateAdded),
    price: localizePrice(price),
  };

  localizedCart.push(localizedItem);
});

// Do ✅
let localizedCart = cart.map(({ dateAdded, price, ...item }) => ({
  ...item,
  dateAdded: localizeDate(dateAdded),
  price: localizePrice(price),
}));

As you can see, the use of .map() saves us from creating an empty array and pushing each modified element into it by providing a much elegant solution.




.filter()

Array.prototype.filter() is another method that returns a new array of elements each satisfying the provided test condition. Using the same cart data, imagine we wanted to filter all elements that have a price greater than or equal to $15.00:


// Don't ❌
let filteredCart = [];
cart.forEach((item) => {
  if (Number(item?.price) >= 15) {
    filteredCart.push(item);
  }
});

// Do ✅
let filteredCart = cart.filter(({ price }) => Number(price) >= 15);

The expected result will be:



.find() & .findIndex()

Array.prototype.find() and Array.prototype.findIndex() are pretty self-explanatory. The first method returns the value of the first element that satisfies a given test condition, while the second returns the index of this element. In the following example, we are interested in finding the index and the value of the first element whose quantity is less than 2:


// Don't ❌
let maybeItem = null;
let maybeIndex = 0;
cart.forEach((item, index) => {
  if (Number(item?.quantity) < 2) {
    maybeItem = item;
    maybeIndex = index;
    return;
  }
});

// Do ✅
const isQuantityLessThan2 = ({ quantity }) => Number(quantity) < 2;
let maybeItem = cart.find(isQuantityLessThan2);
let maybeIndex = cart.findIndex(isQuantityLessThan2);

The results are:



.reduce()

Array.prototype.reduce() is a very useful method that applies some logic to each iteration while accumulating results of all previous iterations. In this context, the “accumulator” could be a new object, array, or a simple variable. In this example, we are interested in calculating the total price of all cart items:


// Don't ❌
let totalPrice = 0;
cart.forEach(({ price, quantity }) => {
  totalPrice += price * quantity;
});

// Do ✅
let totalPrice = cart.reduce(
  (sum, { price, quantity }) => sum + price * quantity,
  0 // This is the initial value of the accumulator (sum)
);

And the result of our reducer:



Obviously, .reduce() has much powerful use-cases than computing sums, however, I chose to keep examples simple in this article to cover as much concepts as possible.


.every() & .some()

Array.prototype.every() and Array.prototype.some() check if every or some elements satisfy a test condition respectively. These methods should be used instead of .filter() and .find()— and obviously traditional loops — whenever you’re only interested in a “Yes or No” answer rather than the actual values.


const isItemUnavailable = ({ isItemAvailable }) => !isItemAvailable; // Our test condition

// Don't ❌
let doesContainUnavailableItems = false;
cart.forEach(({ isItemAvailable }) => {
  if (!isItemAvailable) {
    doesContainUnavailableItems = true;
    return;
  }
});

// Don't ❌
let doesContainUnavailableItems = cart.filter(isItemUnavailable).length > 0;

// Don't ❌
let doesContainUnavailableItems = Boolean(cart.find(isItemUnavailable));

// Do ✅
let doesContainUnavailableItems = cart.some(isItemUnavailable);


.sort()

The last method to cover in this section is Array.prototype.sort() which takes a comparator function as an argument and returns a sorted array. The default sort order is ascending when no custom comparator is provided.


Chaining methods

Now that we have seen different practices of array methods, let’s use them together. Suppose you had to modify the cart array to satisfy the following constraints:

  • All items should be marked as available
  • We only want to present itemId and netPrice
  • Items should be sorted in ascending order of quantities

We can easily achieve all the constraints by chaining array methods:



cart
  .filter(({ isItemAvailable }) => isItemAvailable) // Get available items
  .sort((itemA, itemB) => itemA.quantity - itemB.quantity) // Sort by ascending order of quantity
  .map(({ itemId, quantity, price }) => ({
    itemId,
    netPrice: quantity * price,
  })); // Only present itemId and netPrice

// Result:
// [
//   { itemId: "EvxqG2f4Edd55DpRdcJ8y", netPrice: 24.98 },
//   { itemId: "szXwXmq9i0HpHS1_OlxxN", netPrice: 20.24 },
//   { itemId: "hKhoBgnD0cccymkGlGHaZ", netPrice: 45.03 },
// ];

Keep in mind that over-chaining might increase time complexity and affect performance, so make sure you optimize these calls.

Bonus: Using ES6 features


Destructuring

const [apple, banana, cherries] = ["🍏", "🍌", "🍒"];
console.log(apple); // 🍏
console.log(banana); // 🍌
console.log(cherries); // 🍒

Default values

const [apple, banana, cherries, mango = "🥭"] = ["🍏", "🍌", "🍒"];
console.log(mango); // 🥭

Skipping values

const [apple, , cherries] = ["🍏", "🍌", "🍒"];
console.log(apple); // 🍏
console.log(cherries); // 🍒

Spread operator

const [apple, ...others] = ["🍏", "🍌", "🍒"];
console.log(apple); // 🍏
console.log(others); // ["🍌", "🍒"]