Sign in
Log inSign up

How I work with arrays

Zell Liew's photo
Zell Liew
·Jun 25, 2020·

5 min read

There are many flavours to arrays in JavaScript. The possible methods you to create or change arrays are: unshift, shift, push, pop, splice, concat, slice, destructuring, rest operators, and spread operators.

There are also looping methods like for, forEach, map, filter, reduce, find, findIndex.

17 different flavours! 😱.

I want to document how I choose the array methods I use. This should help you understand how to pick methods.

On Mutation

Never mutate arrays. It can break your code when you’re unaware. And these kinds of bugs are hard to find.

If you need to mutate arrays, always use slice to make a copy before you mutate them.

const array = [1, 2, 3]
const copy = array.slice()

// Use these methods only after you slice
copy.push(4)
copy.pop()
copy.unshift(0)
copy.shift()
copy.splice(0, 0, 0)

Adding items to arrays

We can add items in three ways:

  1. Start of an array
  2. End of an array
  3. Middle of an array

Adding items to start

When I add items to the start of an array, I prefer creating a new array with spread operators. This is the cleanest method.

const array = [3, 4, 5]
const after = [1, 2, ...array]

console.log(after) // [1, 2, 3, 4, 5]

Adding items to end

When adding items to the end of an array, I also prefer creating a new array with spread operators.

const array = [3, 4, 5]
const after = [...array, 6, 7]

console.log(after) // [3, 4, 5, 6, 7]

Adding to the middle

I prefer splice when adding items to the middle of an array. I do this because using slice alone feels more clunky.

For example, let’s say I have an array of 25 kinds of fruit. I want to add Orange after Pear. But I don’t know where Pear is. I have to find Pear first with indexOf.

const index = fruits.indexOf('Pear')

Now, I can add Orange after Pear. Compare the difference between slice and splice.

// Using Slice
const result = [
  ...fruits.slice(0, index)
  'Orange',
  ...fruits.slice(index + 1)
]
// Using Splice
const result = fruits.slice()
result.splice(index + 1, 0, 'Orange')

splice is much easier to read compared to the slice-only alternative. It takes a while to get used to splice though. (Apparently, liking splice is weird).

Removing items from arrays

We can remove items from arrays in three ways:

  1. From the start of an array
  2. From the end of an array
  3. From the middle of an array

Removing items from the start

When I remove items from the start of an array, I prefer to destructure the array. This is cleaner than unshift or splice.

const array = [1, 2, 3]
const [throwaway, ...result] = array

console.log(result) // [2, 3]

Removing items from the end

When I remove items from the end of an array, I prefer using slice. Here, I can use negative indexes instead of array.length. This makes things much simpler.

const array = [1, 2, 3]
const result = array.slice(0, -2)

console.log(result) // [1]

If I need to remove only 1 or 2 items, then I consider pop. This is friendlier for beginners to read.

const array = [1, 2, 3]
const result = array.slice()
array.pop()

console.log(result) // [1, 2]

Removing items from the middle

I prefer splice over other methods as I described in adding items to the middle.

// Using Slice
const result = [
  ...fruits.slice(0, index)
  ...fruits.slice(index + 1)
]
// Using Splice
const result = fruits.slice()
result.splice(index, 1)

Looping through arrays

When I loop through arrays, I prefer to use map and filter as much as possible. If they can do the job, great!

// Map
const array = [1, 2, 3]
const doubled = array.map(x => x * 2)

console.log(doubled) // [2, 4, 6]
// Filter
const array = [1, 5, 10]
const below6 = array.filter(x => x < 6)

console.log(below6) // [1, 5]

I never reduce if I can map + filter because map + filter is easier to read. I only use reduce when I have to convert the array into a primitive value (usually only with numbers).

// Reducing an array of numbers
const array = [1, 2, 3]
const sum = array.reduce((sum, current) => sum + current, 0)

console.log(sum) // 6

If I need to turn arrays into objects, I prefer using a forEach loop.

const fruits = ['apple', 'apple', 'pear']

// With forEach
const tally = {}
fruits.forEach(fruit => {
  if (tally[fruit]) {
    tally[fruit] += 1
    return
  }
  tally[fruit] = 1
})

console.log(tally)
// {
// apple: 2,
// pear : 1
// }
// with Reduce
const tally = fruits.reduce((tally, fruit) => {
  if (tally[fruit]) {
    tally[fruit] += 1
  } else {
    tally[fruit] = 1
  }
  return tally
}, {})

console.log(tally)
// {
// apple: 2,
// pear : 1
// }

If I need to execute something (like change classes), I prefer forEach. I can also use for...of, but I like forEach more.

const nodes = document.querySelectorAll('.hey')

// With forEach
[...nodes].forEach(node => {
  node.classList.remove('hey')
})

// With for...of
for (const node of nodes) {
  node.classList.remove('hey')
}

When I read forEach, my mind goes like this:

  1. Nodes array.
  2. Loop through the nodes array.
  3. Do something with each node.

When I see for...of, my mind goes like this:

  1. For loop. Okay.
  2. Created a variable called node.
  3. Looping through nodes
  4. Do something with node

for...of just doesn’t flow as smoothly as forEach.

Asynchronous loops

If I can batch asynchronous takes together, I’ll use map then Promise.all.

const array = ['url1', 'url2']
const promises = array.map(url => fetch(url).then(/*...*/))
const results = Promise.all(promises)

console.log(results)
// [
// [results from url1], [results from url2]
// ]

If I use the await keyword, I prefer for...of. I explained why in this article.

async function execute () {
  for (const link of links) {
    await fetch(link).then()
    // Do something...
  }
}

That’s it! I hope this clears things up!


Thanks for reading. This article was originally posted on my blog. Sign up for my newsletter if you want more articles to help you become a better frontend developer.