Skip to main content
JavaScript the Good parts book next to Javascript the definitive guide book

Fun Facts about JavaScript Arrays

JavaScript has a lot of quirks and weird parts, some of these impact the daily work of the average web dev, while some of it belongs to the sort of dark arts, which's only practical application is to create tricky interview questions.

Nonetheless, it's worth knowing these oddities in a language.
Today I was thinking to cover some lesser known behaviors involving JavaScripts's arrays, so let's dive in.

Arrays are actually Objects

We know that curly braces ({}) stand for objects and square brackets ([]) stand for arrays, right?
Well it turns out that arrays are just a special kind of object. You can prove this by checking the type of an array.

javascript
copy
      const dogBreeds = ["Labrador", "Poodle", "German Shepherd"]
console.log(typeof dogBreeds) // object
      

Or an even better option is to log out an array in your browser's console and see that the [[Protype]] points to the built in Array object.

A screenshot of an Array prototype viewed in the console of the browser

Observe all the built in array methods you know and love being listed on the prototype object (the faded color signals that they are read-only).

If you want to know that some unknown piece of data is indeed an array, the easiest way is to use the isArray static method on the Array prototype.

javascript
copy
      const somethingThatMightBeAnArray = ["dog", 42, {foo: 'bar'}]

console.log(Array.isArray(somethingThatMightBeAnArray)) // true

const somethingThatsNotAnArray = { id: 1 }

console.log(Array.isArray(somethingThatsNotAnArray)) // false
      

Now when your accessing an item inside an array with the bracket notation (meaning myArray[0]), you are actually accessing a key of the object, but because object keys can't be integers, under the hood JS actually converts that number you put in between the brackets to a string. So actually myArray[0] and myArray["0"] would yield the same result.

Also note that dot notation won't work, so myArray.0 will fail just as trying to use dot notation to get the value of an object key that uses a "-" character or an integer.

javascript
copy
      const myObject = {"my-key": "A", "20": "B"};
console.log(myObject.20) // Uncaught SyntaxError: missing ) after argument list

console.log(myObject["20"]) // "B"
      

Speaking of keys, - or as we call them in this case array indices - there's another odd behavior that a lot of people don't know about. Namely that if you set an item with an index that's bigger than the currently available last index, JS will "fill up" the items leading up to that index with empty values. Don't believe me? Try it for yourself!

javascript
copy
      const dogBreeds = ["Labrador", "Poodle", "German Shepherd"]
dogBreeds[100] = "Vizsla"
console.log(dogBreeds.length) // 101
      

Observe that all the 97 items between the indices 2 and 100 will give undefined.

I don't really know the exact reason why this is, but if I had to guess it has to do with something relating to the fact that under the hood, JavaScript engines store arrays in some other data structure.

The practical implication is that if you want to dynamically insert new items to an array you should always use the built in push method instead of the bracket notation if you want to be safe and keep the consistency of your indexes.

Arrays are stored by Reference

There's something that tips off beginners and sometimes even experienced programmers coming from other languages.

When trying to compare arrays that look the same you run into a surprise.

javascript
copy
      const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3]

console.log(arr1 === arr2); // false
      

This seems odd…

In other programming languages this would be true. Say in PHP.

php
copy
      $arr1 = [1, 2, 3];
$arr2 = [1, 2, 3];

var_dump($arr1 === $arr2); // true
      

Or in Python.

python
copy
      from array import*

a = array("i", [1, 2, 3])
b = array("i", [1, 2, 3])

print(a == b) # True
      

So what's going on here? If you understand that arrays are actually objects, then this whole thing will make sense.

In JavaScript objects are stored by reference and not by value like primitive data types. So that means that even if the values (and keys) are the same, internally they are stored on a different chunk of memory, so when comparing them for equality they will always return false, unless you compare the same object reference with itself.

Now again this has a lot of real world implications. For instance if you just reassign an existing array to a new variable and then start mutating the values you are in for some surprises.

javascript
copy
      const dogBreeds = ["Poodle", "Labrador"]
const copiedDogBreeds = dogBreeds

copiedDogBreeds.push("Labradoodle")

console.log(copiedDogBreeds[2]) // "Labradoodle"
console.log(dogBreeds[2]) // "Labradoodle"
      

You would initially expect that you've only modified the copiedDogBreeds array, but as you can see our original array also got "Labradoodle" as its third item.

To fix this you'll need to make an actual copy of the object. One way is to use the slice array method without any arguments

javascript
copy
      const dogBreeds = ["Poodle", "Labrador"]
const copiedDogBreeds = dogBreeds.slice()

copiedDogBreeds.push("Labradoodle")

console.log(copiedDogBreeds[2]) // "Labradoodle"
console.log(dogBreeds[2]) // undefined
      

Or better yet use the spread syntax!

javascript
copy
      const dogBreeds = ["Poodle", "Labrador"]
const copiedDogBreeds = [...dogBreeds]

copiedDogBreeds.push("Labradoodle")

console.log(copiedDogBreeds[2]) // "Labradoodle"
console.log(dogBreeds[2])
      

But wait! There's more…

So you would think that the above examples create a totally new JavaScript array object, right? Wrong! Both of these methods only create a so-called shallow copy, which still shares the same underlying values of the source object.

Now the practical consequence of this is that if we modify any nested value then we will still overwrite the values in the original array.

javascript
copy
      const numbersAndLetters = [[1, 2, 3], ["a", "b", "c"]]
const copiedNumbersAndLetters = [...numbersAndLetters]

copiedNumbersAndLetters[1][0] = "A"

console.log(numbersAndLetters[1][0]) // "A"
console.log(copiedNumbersAndLetters[1][0]) // "A"
      

The only way around this is to make a deep copy, which is - you guessed it - a totally new array object with no connection whatsoever to the original.
The easiest way to do this is to serialize it to a JSON string then parse it back into a JavaScript object.

javascript
copy
      const numbersAndLetters = [[1, 2, 3], ["a", "b", "c"]]
const copiedNumbersAndLetters = JSON.parse(JSON.stringify(numbersAndLetters))

copiedNumbersAndLetters[1][0] = "A"

console.log(numbersAndLetters[1][0]) // a
console.log(copiedNumbersAndLetters[1][0]) // A
      

Summary

In essence you need to remember that arrays in JavaScript are actually objects with the indices as their keys and as objects they are stored by reference.

I hope you've learned a thing or two while reading this, cause I sure did while writing it.