Arrays

Predefined objects

In JavaScript arrays are not of an explicit data type. JavaScript provides a predefined Array object. The keys are called indexes, being nonnegative integers starting with zero. The indexed properties are called elements. Next to elements, an array can also have "regular" object properties, like the Array.prototype.length property, which sets or returns the number of elements in an array.


const myArray = ["value1", "value2"];

console.log(myArray[0]); // logs: "value1"
console.log(myArray["0"]); // logs: "value1"
console.log(myArray[1]); // logs: "value2"
console.log(myArray[2]); // logs: undefined

Arrays should not be confused with array-like objects, which also have indexed properties and a length property. Arrays are predefined objects with their own specific inherited methods and a specific literal syntax, and unlike array-like objects, arrays are inherently iterable. Array-likes were covered in the previous chapter.


const arrayLike = {
  0: "pit",
  1: "pot",
  2: "pat",  
  length: 3,
}

for (let elem of arrayLike) {
  console.log(elem);
}
// Logs: Uncaught TypeError: arrayLike is not iterable

const arrayLike = {
  0: "pet",
  1: "pot",
  2: "put",  
  length: 3,
}

Array.prototype.forEach.call(arrayLike, (element) => {
    console.log(element);
  });
// logs:
// "pet"
// "pot"
// "put"

arrayLike.forEach(element => console.log(element)); // Uncaught TypeError: arrayLike.forEach is not a function

Creating an array

To create an array you can use the constructor function (with or without the keyword new), but generally the preferred way is to use an array literal (aka array initializer).


const myArray1 = ["value1", "value2", 3];

// ... which has exactly the same effect as:
const myArray2 = new Array("value1", "value2", 3);
// or:
const myArray3 = Array("value1", "value2", 3);

If you use the constructor function with a single argument that is a number, the argument is interpreted as the length of the array, and not as the array's single element with a number value. In other words, it creates an empty array of the specified length; in the example below, an array of 3 empty slots. If the argument is a non-integer number, the creation results in a RangeError.


const myArray = new Array(3);
console.log(myArray[0]); // logs: undefined
console.log(myArray[1]); // logs: undefined
console.log(myArray.length); // logs: 3

// The above array creation has exactly the same effect as:
const myArray2 = [];
myArray2.length = 3;

// What the programmer possibly intended to create:
const myArray3 = [3];
console.log(myArray3[0]); // logs: 3
console.log(myArray3.length); // logs: 1

const myArray4 = new Array(3, 2);
console.log(myArray4); // logs: [ 3, 2 ]

const myArray5 = new Array(3.2); // logs: RangeError: invalid array length

You can also use the Array.of() method to create a new array. This method handles a single element with a number value as expected.


const myArray1 = Array.of(3);
const myArray2 = Array.of(3.2);
const myArray3 = Array.of(3, "a", "b");

console.log(`${myArray1[0]}, ${myArray2[0]}, ${myArray3[1]}`); // logs: "3, 3.2, a"

Maximum length

According to ECMAScript specification, an array can have a maximum of 232 − 1 = 4 294 967 295 elements (maximum index: 4 294 967 294). However, on an arbitrary user agent an application may work fine with (far) more elements in an array. The length property though, is always an integer less than 232.


const myArray = [];

myArray[5000000000] = "hello";
console.log(myArray[5000000000]); // logged on my machine: "hello"
console.log(myArray.length); // logs: 0

myArray.length = 4294967296; // logs:  RangeError: invalid array length

Accessing elements

The elements of an array cannot be accessed by means of the dot-notation since indexes are not valid identifiers. Only the square bracket notation can be used. If the key is an integer, it will be interpreted as the index of an array element, if it is a non-integer number, it will be interpreted as a key of a "regular" property of the array. The square bracket notation can also be used to access computed indexes.


const myArray = ['one', 'two', 'three'];
console.log(myArray[2]); // logs: "three"
console.log(myArray["0"]); // logs: "one"
console.log(myArray['length'] === myArray.length); // logs: true

myArray[3] = "four";
myArray[3.2] = "five";

console.log(myArray); // logs: ['one', 'two', 'three', 'four']
console.log(myArray[3.2]); // logs: 'five'

console.log(myArray[myArray.length - 1]); // logs: "four"

Finding and checking elements in an array

You can use the indexOf() method to find the index of a given element. indexOf() returns -1 if the element is not present.


const myArray = ["pit", "pet", "pot"];

console.log(myArray.indexOf('pit')); // logs: 0
console.log(myArray.indexOf('pet')); // logs: 1
console.log(myArray.indexOf('pat')); // logs: -1

You can check if an array contains a certain element by, again, using indexOf() or by using the includes() method.


const myArray = ["pit", "pet", "pot"];

console.log(myArray.includes('pit')); // logs: true
console.log(myArray.includes('pat')); // logs: false

console.log(myArray.indexOf('pit') !== -1); // logs: true
console.log(myArray.indexOf('pat') !== -1); // logs: false

You can check if an array index exists by either using the hasOwn() method or using the in operator.


const myArray = ["pit", "pet", "pot"];

console.log( Object.hasOwn(myArray, 2) ); // logs: true
console.log( Object.hasOwn(myArray, 3) ); // logs: false
console.log(0 in myArray); // logs: true
console.log(3 in myArray); // logs: false
console.log("pot" in myArray); // logs: false
console.log("length" in myArray); // logs: true
console.log(Symbol.iterator in myArray); // logs: true

Sparse arrays

Arrays can contain "empty slots". Empty slots are not filled with an element, but the array's length property will return the number of elements including the empty slots. Empty slots are not the same as elements with the value set to undefined, nor are they the same as elements with an index equal or larger than the array's length. Arrays with empty slots, i.e., gaps in the sequence of their indexes, are called sparse arrays, as opposed to dense arrays.


const arr1 = [1, 2, 3]; // dense array
console.log(arr1[3]); //logs: undefined
console.log(arr1.length); //logs: 3

const arr2 = [1, 2, undefined, 3 ]; // dense array
console.log(arr2[2]); //logs: undefined
console.log(arr2.length); //logs: 4
console.log(arr2); //logs: [ 1, 2, undefined, 3 ]
console.log(2 in arr2); // logs: true

const arr3 = [1, 2, , 3 ]; // sparse array
console.log(arr3[2]); //logs: undefined
console.log(arr3.length); //logs: 4
console.log(arr3); //logs: [ 1, 2, <1 empty slot>, 3 ]
console.log(2 in arr3); // logs: false

const arr4 = [1, 2, 3, ]; // dense array
console.log(arr4); //logs: [ 1, 2, 3 ]

const arr5 = [, 1, 2, 3]; // sparse array
console.log(arr5[0]); //logs: undefined
console.log(arr5.length); //logs: 4

const arr1 = new Array(3); // sparse array
console.log(arr1[2]); //logs: undefined
console.log(arr1.length); //logs: 3
console.log(arr1); //logs: [ <3 empty slots> ]

const arr2 = [1, 2, 3]; // dense array
arr2[4] = 5;  // arr2 is now a sparse array 
console.log(arr2); //logs: [ 1, 2, 3, <1 empty slot>, 5 ]

const arr3 = [1, 2, 3]; // dense array
arr3.length = 5;  // arr3 is now a sparse array 
console.log(arr3); //logs: [ 1, 2, 3, <2 empty slots> ]
console.log(arr3.length); //logs: 5

Also deleting elements from an array using the delete operator leaves empty slots and makes the array a sparse array. Later this section more about deleting elements.

Adding elements

Elements can simply be added by using the square bracket notation:


const myArray = [];

myArray[0] = "foo";
console.log(myArray); // logs: [ "foo" ]

// adding to the end of the array:
myArray[myArray.length] = "bar";
console.log(myArray); // logs: [ "foo", "bar" ]

The push() method can be used to add one or more elements to the end of an array.


const myArray = ["pit"];
myArray.push("pet", "pot");
console.log(myArray); // logs: [ "pit", "pet", "pot" ]

The unshift() method can be used to add one or more elements to the beginning of an array.


const myArray = ["pot"];
myArray.unshift("pit", "pet");
console.log(myArray); // logs: [ "pit", "pet", "pot" ]

Deleting elements

Elements can be deleted from an array by using the delete operator. However, this creates a sparse array. The deleted element will leave an empty slot. Alternatively you can set the value of the element to undefined, which does not create a sparse array.


const arr1 = ["foo", "bar", "baz"];
delete arr1[2];
console.log(2 in arr1); // logs: false
console.log(arr1.length); // logs: 3

const arr2 = ["foo", "bar", "baz"];
arr2[2] = undefined;
console.log(2 in arr2); // logs: true
console.log(arr2.length); // logs: 3

Truly deleting an element from an array (deleting both the element and the slot) can be achieved by using Array method splice(), that removes elements from an array and, optionally, replaces them.


const arr = ["foo", "bar", "baz", "foobar"];

// one element is removed starting from index 2:
arr.splice(2, 1); 
console.log(arr); // logs: [ "foo", "bar", "foobar" ]

// the last two items are removed:
arr.splice(-2); 
console.log(arr); // logs: [ "foo" ]

// remove element with value "baz":
const arr = ["foo", "bar", "baz", "foobar"];
arr.splice(arr.indexOf("baz"), 1); 
console.log(arr); // logs: [ "foo", "bar", "foobar" ]

// replace elements:
const arr = ["foo", "bar", "baz", "foobar"];
const removedItems = arr.splice(1, 2, "fizz", "buzz"); 
console.log(arr); // logs: [ "foo", "fizz", "buzz", "foobar" ]

console.log(removedItems); // logs: [ "bar", "baz" ]

Method pop() can be used to remove only the last element from an array. Method shift() can be used to remove only the first element from an array.

Also the array's length property can be used to truly delete elements from the end of the list, i.e., to truncate the array.


const onWheels = ["car", "bike", "skateboard"];

onWheels.length = 2;
// onWheels.splice(2); // this does the same
// onWheels.pop(); // this does the same
console.log(onWheels); // logs: [ "car", "bike" ] // "skateboard" has been removed

onWheels.length = 0;
// onWheels.splice(0); // this does the same 
console.log(onWheels); // logs: [ ] // empty array

Note that assigning 0 to the length property is not the same as assigning an empty array to the variable. The former alters the existing array, the latter replaces the existing array with a new (empty) array.


let onWheels = ["car", "bike", "skateboard"];
const onWheelsCopy = onWheels;

onWheels = [];
console.log(onWheels); // logs: [ ]
console.log(onWheelsCopy); // logs: ["car", "bike", "skateboard"]

const onWheels = ["car", "bike", "skateboard"];
const onWheelsCopy = onWheels;

onWheels.length = 0;
console.log(onWheels); // logs: [ ]
console.log(onWheelsCopy); // logs: [ ]

Multi-dimensional arrays

An array element can contain another array. This nesting of arrays can be used to create multi-dimensional arrays. In a multi-dimensional array each element is again an array. This can be multiple layers deep. The next example creates a two-dimensional array by means of a loop.


const myArray = [];
for (let i = 0; i < 3; i++) {
  myArray[i] = [];
  for (let j = 0; j < 3; j++) {
    myArray[i][j] = `[${i}, ${j}]`;
  }
}

console.table(myArray);
/* logs:
[0, 0] [0, 1] [0, 2]
[1, 0] [1, 1] [1, 2]
[2, 0] [2, 1] [2, 2]
*/

BTW. console.table() is used to displays the array as a table.

An other example of a two-dimensional array:


const powers = [];
for (let x = 0; x < 10; x++) {
  powers.push([
    x ** 2,
    x ** 3,
  ]);
}

console.log(powers.join('\n'));
// logs:
// 0,0
// 1,1
// 4,8
// 9,27
// 16,64
// 25,125
// 36,216
// 49,343
// 64,512
// 81,729

BTW. The Array.prototype.join() method is used to convert the array into a string.

Iterating over arrays

The Array object has a number of methods. In this section we will use Array method forEach(). Additionally there are methods to sort, filter and find certain elements, alter the object or create a new object based on an excising one. This is a list of Array methods. A number of them are explained in this chapter.

A number of these methods and some loops can be used to iterate over the elements of an array. Elements of an array are enumerable own properties, so we can use methods and loops that enumerate properties to iterate over the elements of an array. However, it will also iterate over all "regular" enumerable properties of the array (if they exist) and it will be in an arbitrary order. Object.keys(), for...in, Object.assign etc. use property enumeration, as we have seen. Iterating exclusively over array elements generally does not use property enumeration.

Iterating constructs, like the for...of loop or spread syntax, that iterate over array elements typically use the array's iterator. This is a built-in method of the Array.prototype. Methods that use the array's iterator visit the elements of an array in a successive ascending order.

Other Array's iterative methods, e.g. the forEach() method, do not use the Array's iterator. They use the fact that properties are indexed, in combination with the length property, which can be easily demonstrated using a common for loop:


const myArray = ["pet", "pot", "pit"];

for (let i = 0; i < myArray.length; i++) {
   console.log(myArray[i]);
}
// logs:
// "pet"
// "pot"
// "pit"

const myArray = [1, 2 , 3];
myArray.prop = "enumValue";
let elmArr = [];

for (let i = 0; i < myArray.length; i++) { elmArr.push(myArray[i]) }
console.log(elmArr); // logs: [ 1, 2, 3 ]
elmArr.length = 0;

myArray.forEach(elementValue => elmArr.push(elementValue));
console.log(elmArr); // logs: [ 1, 2, 3 ]
elmArr.length = 0;

myArray.forEach((elementValue, key) => elmArr.push(`${key}: ${elementValue}`));
console.log(elmArr); // logs: [ "0: 1", "1: 2", "2: 3" ]
elmArr.length = 0;

// Array's iterator:

for (const elementValue of myArray) { elmArr.push(elementValue) }
console.log(elmArr); // logs: [ 1, 2, 3 ]
elmArr.length = 0;

elmArr = [...myArray];
console.log(elmArr); // logs: [ 1, 2, 3 ]
elmArr.length = 0;

// Property enumeration:

for (const key in myArray) { elmArr.push(myArray[key]) };
console.log(elmArr); // logs: [ 1, 2, 3, "enumValue" ]

console.log( Object.assign({}, myArray) ); // logs: { 0: 1, 1: 2, 2: 3, prop: "enumValue" }

console.log(Object.keys(myArray)); // logs: [ "0", "1", "2", "prop" ]

It is possible to set the [[enumerable]] attribute of an element to false, but this has only effect on constructs that enumerate.


const myArray = [1, 2, 3];
const elmArr = [];

Object.defineProperty(myArray, '3', {
  value: 4,
  writable: true,
  enumerable: false,
  configurable: true
});

console.log( myArray[3] ); // logs: 4

myArray.forEach(element => elmArr.push(element));
console.log(elmArr); // logs: [ 1, 2, 3, 4 ]
elmArr.length = 0;

for (const element of myArray) { elmArr.push(element) }
console.log(elmArr); // logs: [ 1, 2, 3, 4 ]
elmArr.length = 0;

// Property enumeration:

for (const key in myArray) { elmArr.push(myArray[key]) };
console.log(elmArr); // logs: [ 1, 2, 3 ]

Some operations will skip empty slots, others will treat empty slots as undefined and yet others will recognize the empty slot:


const myArray = [1, , 3]; // sparse array
const elmArr = [];

console.log(myArray[1]); // Logs: "undefined"

// empty slots are treated as undefined:

for (let i = 0; i < myArray.length; i++) { elmArr.push(myArray[i]) }
console.log(elmArr); // logs: [ 1, undefined, 3 ]
elmArr.length = 0;

for (let element of myArray) { elmArr.push(element) }
console.log(elmArr); // logs: [ 1, undefined, 3 ]
elmArr.length = 0;

const copiedArray = [...myArray];
console.log(copiedArray); // logs: [ 1, undefined, 3 ]

// empty slots are skipped:

myArray.forEach(element => elmArr.push(element));
console.log(elmArr); // logs: [ 1, 3 ]
elmArr.length = 0;

[1, undefined, 3].forEach(element => elmArr.push(element));
console.log(elmArr); // logs: [ 1, undefined, 3 ]
elmArr.length = 0;

console.log(Object.keys(myArray)); // [ "0", "2" ]

for (const key in myArray) { elmArr.push(myArray[key]) };
console.log(elmArr); // logs: [ 1, 3 ]

const objectSpread = { ...myArray };
console.log(objectSpread); // logs: { 0: 1, 2: 3 }

// empty slots are recognized:

console.log(myArray.slice()); // logs: [ 1, <1 empty slot>, 3 ]

Copying and merging arrays

As mentioned before, in JavaScript arrays are objects. We covered cloning and merging objects earlier in this tutorial. We learned that there is a lot to consider when cloning or merging objects: shallow or deep copying, involving only own or also inherited properties, only enumerable or also non-enumerable properties, string keyed and/or symbol keyed properties, copy prototypes or not, dealing with getters and setters 😵. Copying an array just adds a few more considerations: only elements or also "regular" properties and how to deal with empty slots and multi-dimensional arrays 😵🥴🤪.

You can use for...in (using property enumeration) to shallow copy an array, copying both elements and "regular" properties, but not symbol keyed properties and skipping empty slots (see previous section).

In the previous section we already covered a few ways to shallow copy only elements of an array into a new array, like using a for loop, a for...of loop, spread syntax or forEach(). Other methods that are able to shallow copy an array (only elements) are Array.from(), Array.prototype.slice() or Array.prototype.concat(). Array.from() and Array.slice() can also create an adapted copy.


const myArray = [1, , 3]; // sparse array
let copiedArray = [];

copiedArray = Array.from(myArray);
console.log(copiedArray); // logs: [ 1, undefined, 3 ]

// create an adapted copy:
console.log(Array.from(myArray, x => x + x)); // logs: [ 2, NaN, 6 ]

copiedArray = myArray.slice();
console.log(copiedArray); // logs: [ 1, <1 empty slot>, 3 ]

copiedArray = myArray.concat();
console.log(copiedArray); // logs: [ 1, <1 empty slot>, 3 ]

BTW. Note that there are more methods available to create a (adapted) shallow copy of an array like Array.prototype.filter() or Array.prototype.reduce().

BTW. Array.from() can be used to create a (adapted) shallow-copied Array from any iterable or array-like object.

Spread syntax

A very convenient way to shallow copy or merge arrays is spreading elements of an array into a new array. This uses the iterator of the array, which means that only elements and no other properties are copied.

We also used spread syntax to spread properties from a provided object into a new object to make a shallow copy of an object. In this case it copies only own enumerable properties. Also spreading an array into an object uses property enumeration, not the array's iterator.


const myArray = [1, 2, 3];
myArray.prop = "enumValue";

const copiedArray = [...myArray];
console.log(copiedArray); // logs: [ 1, 2, 3 ]

const objectSpread = { ...myArray };
console.log(objectSpread); // logs: { 0: 1, 1: 2, 2: 3, prop: "enumValue" }

const myArray = [1, , 3]; // sparse array
const copiedArray = [...myArray];
console.log(copiedArray); // logs: [ 1, undefined, 3 ]

Merging arrays:


const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];

const arr3 = arr1.concat(arr2);
const arr4 = [...arr1, ...arr2];

console.log(arr3, arr4); // logs: [ 0, 1, 2, 3, 4, 5 ] [ 0, 1, 2, 3, 4, 5 ]

let arr1 = ["pit", "put", "pet"];
const arr2 = ["hot", "hat"];

arr1 = ["pot", "pat", ...arr1, ...arr2, "hit", "hut"];

console.info(arr1); // logs: [ "pot", "pat", "pit", "put", "pet", "hot", "hat", "hit", "hut" ]

BTW. Note that in the last example arr1 gets assigned a new array value, instead of modifying the original arr1 array.

You can use Array.prototype.push() to append a number of elements to an array. It adds its arguments to the end of an array. If you pass an array to push(), it will add that array as a single element, instead of adding the elements individually. As mentioned earlier, we can use Function.prototype.apply() as an equivalent for spreading an array in function parameters. So, we can use apply() or spread syntax on method push() to spread an array into another array as an alternative for only using the spread syntax. However, now it appends to the existing array!


let arr1 = ["pit", "put", "pet"];
const arr2 = ["hot", "hat"];

arr1.push.apply(arr1, arr2);

console.info(arr1); // logs: [ "pit", "put", "pet", "hot", "hat" ]

let arr1 = ["pit", "put", "pet"];
const arr2 = ["hot", "hat"];

arr1.push(...arr2);

console.info(arr1); // logs: [ "pit", "put", "pet", "hot", "hat" ]

Array destructuring and rest elements

Also with array destructuring (see next chapter) and rest elements you can create a (altered) shallow copy.


const myArray = [1, , 3]; // sparse array
const [...copiedArray] = myArray;
console.log(copiedArray)     // logs: [ 1, undefined, 3 ]

const myArray = [1, 2, 3];
const [a, ...newArray] = myArray;
console.log(newArray)     // logs: [ 2, 3 ]

Deep cloning

All discussed methods above create shallow copies, meaning that copying goes one level deep. If an element yet again is a reference to an array (or other object), both the original and the copy share the same lower level elements. This may be undesirable when copying nested arrays. You can create deep copies using the structuredClone() method (which also copies possible "regular" properties).


const myArray = [[1,2], 3, 4];
const copiedArray = [...myArray];

console.log( myArray[0] ); // logs:  [ 1, 2 ]
console.log( copiedArray[0] ); // logs: [ 1, 2 ]

copiedArray[0][0] = "altered";

console.log( myArray[0] ); // logs: [ "altered", 2 ]
console.log( copiedArray[0] ); // logs: [ "altered", 2 ]

const myArray = [[1,2], 3, 4];
const deepCopiedArray = structuredClone(myArray);

console.log( myArray[0] ); // logs:  [ 1, 2 ]
console.log( deepCopiedArray[0] ); // logs: [ 1, 2 ]

deepCopiedArray[0][0] = "altered";

console.log( myArray[0] ); // logs: [ 1, 2 ]
console.log( deepCopiedArray[0] ); // logs: [ "altered", 2 ]