Arrays

An array is a specialized JavaScript object in the form of an ordered dynamic collection of values. Each value is called an element, which has a numeric position in the array called its index.

JavaScript arrays are untyped, meaning each array element can be of any type, and different elements of the same array may be of different types. Typed JavaScript arrays have a fixed length and a fixed numeric type, which allows high-performance access.

JavaScript arrays are zero-based and use a 32-bit index, with the maximum size of 4,294,967,295 elements. Arrays inherit properties from Array.prototype, which defines a rich set of array methods.

Creating arrays

The simplest way to create an array is with an array literal.

let empty = []; // array with no elements
let misc = [1.1, true, 'a',]; // trailing comma is allowed
let sparse = [1,,3]; // sparse array

The values in an array may be arbitrary expressions.

let base = 100;
let table = [base, base+1, base+2];

The spread operator can interpolate one array into another.

let original = [1, 2, 3];
let copy = [...original]; // spread operator
copy[0] = 0;
original[0]; // => 1

let letters = [...'hello world']; // turn string into array
[...new Set(letters)]; // => ['h', 'e', 'l', 'o', ' ', 'w', 'r', 'd']

Arrays can be created with the Array() constructor. When called with a single numeric argument, an array is created with the specified length. When two or more arguments are passed to the constructor or when a single argument is not numeric, a new array is created with specified elements.

let arrEmpty = new Array();
let arr10 = new Array(10);
arr10.length; // => 10
let arr4 = new Array(3, 2, 1, 'test');

Lastly, arrays can be created by using Array.of() and Array.from() array factory methods. Array.of() returns a new array from its argument values.

Array.of(); // => []
Array.of(10); // => [10]
Array.of(1,2,3); // => [1, 2, 3]

Array.from() returns an array from an iterable or array-like objects. The second argument is the function to process each element while the array is being built.

let aCopy = Array.from(original);
let pCopy = Array.from(original, element => {
  return 'el' + element;
});

Reading and writing array elements

When you index an array using negative or floating-point number, the number is converted to a string, which is treated as a regular object property. The length property of the array does not change in this case. When you query a non-existing property or index from an array, you get undefined without any error (same as with objects).

let a = [true, false];
a[2]; // => undefined
a[-1] = 'negative';
a[1.3] = 'float';

JavaScript array may be sparse, which means there may be gaps in the array index. For non-sparse arrays, the length property specifies the number of elements in the array.

let aSparse = []; // empty array
aSparse[1000] = 0; // length is set to 1001
let a1 = [,]; // array with no elements and length 1
let a2 = [undefined]; // array with one undefined element
0 in a1; // => false
0 in a2; // => true

The length property of an array is always greater than its index.

let arr = [1, 2, 3, 4, 5];
arr.length = 3; // arr is now [1, 2, 3]

Iterating arrays

The easiest way to loop through array elements is with for/of loop, which returns elements in ascending order.

let chars = [...'hello', , ...'world'];
let output = '';
for (let char of chars) {
  output += char;
}
output; // => 'helloundefinedworld'

You can obtain the index of an array with its entries() method.

output = '';
for (let [index, char] of chars.entries()) {
  output += index % 2 ? char : index;
}
output; // => '0e2l4undefined6o8l10'

The forEach() method of an array accepts a function as an argument that is invoked on each element of the array, skipping the undefined elements of sparse arrays. forEach() invokes the function with three arguments: the value of the array element, the index of the array element, and the array itself.

output = '';
chars.forEach(char => {
  output += char.toUpperCase();
});
output; // => 'HELLOWORLD'

let data = [1, 2, 3, 4, 5];
data.forEach(function(v, i, a) { a[i] = v + 1; });
data; // => [2, 3, 4, 5, 6]

The for loop can also be used to iterate over array elements.

output = '';
for (let i = 0; i < chars.length; i++) {
  let char = chars[i];
  if (/[aeiou]/.test(char)) {
    output += char;
  }
}
output; // => 'eoundefinedo'

The map() method invokes a function on each element and returns an array containing the values returned by the function. It does not modify the original array. If the array is sparse, the function is not called on missing elements, but the returned array will be sparse in the same way as the original. The specified function is invoked with three arguments: element, index, and the array itself.

let numbers = [1, 2, 3, 4];
numbers.map( x => x*x ); // => [1, 4, 9, 16]

The filter() method returns an array containing a subset of elements in the original array. It accepts a predicate function (which returns true/false) with three arguments: element, index, and the array itself. If the return value is true, then the element is added to the returned array. filter() method skips missing elements in sparse arrays and always returns a dense array.

let digits = [1,2,3,,null,6,7];
digits.filter(x => x > 2 ); // => [3, 6, 7]
digits.filter((x, i) => i%2); // => [2, 6]
digits.filter(() => true); // => [1, 2, 3, null, 6, 7]
digits.filter(x => x !== undefined && x !== null); // => [1, 2, 3, 6, 7]

The find() and findIndex() methods iterate through the array by invoking a predicate function. When the function return true, the iteration stops and find() returns the matching element, while findIndex() returns the index of the matching element. If no matching element is found, find() return null, and findIndex() returns -1.

let values = [1,4,,null,6,7];
values.find(x => x%2 === 0); // => 4
values.find(x => x%5 === 0); // => null
values.findIndex(x => x === 6); // => 4
values.findIndex(x => x < 0); // => -1

The every() method is like mathematical "for all" $\forall$ quantifier: it returns true if the predicate function returns true for all elements in the array, and returns false once the predicate function returns false for an element. As soon as the value to return is know, every() stops iterating over array elements. If the array is empty, every() always returns true.

let nums = [1,4,,null,6,7];
nums.every(x => x < 10); // => true
nums.every(x => x%2 === 0); // => false
[].every(x => false); // => true

The some() method is like mathematical "there exists" $\exists$ quantifier: it returns true if there exists at least one element in the array for which the predicate function returns true and returns false if the predicate function returns false for all elements of the array. As soon as the value to return is know, some() stops iterating over array elements. If the array is empty, some() always returns false.

let ages = [1,4,,null,6,7];
ages.some(x => x%2 === 0); // => true
ages.some(isNaN); // => false
ages.some(x => x === null); // => true
ages.some(x => x === undefined); // => false
[].some(x => true); // => false

The reduce() method iterates over array elements to combine them into a single value. The first argument to reduce() method is the function that takes accumulator, element, index, and array itself as its arguments to combine the values and return the updated accumulator value. The second optional argument is the initial accumulator value. If the second argument is not provided, then the first element becomes the initial value for the accumulator and the iteration starts from the second element. When reduce() is called on an empty array with no initial value for the accumulator, it causes an error.

let marks = [1,4,,null,6,7];
marks.reduce((acc, val) => acc + val, 0); // => 18
marks.reduce((acc, val) => acc * val, 1); // => 0
marks.reduce((a, b) => a > b ? a : b); // => 7
marks.reduce((acc, val, idx, arr) => {
  return acc + idx + val;
}, 0); // => 31

The reduceRight() method works like reduce() method except that it iterates array elements from highest index to lowest. Use reduceRight() when the reduction operation has right-to-left associativity.

let powers = [4,3,2];
powers.reduceRight((acc, val) => Math.pow(val, acc)); // => 262144

Multidimensional arrays

JavaScript does not support multidimensional arrays, but you can use an array of arrays to simulate them.

let matrix = [];
for (let i = 0; i < 10; i++) {
  let row = [];
  for (let j = 0; j < 10; j++) {
    row[j] = i * j;
  }
  matrix[i] = row;
}
matrix[6][8]; // => 48

Flattening arrays

The flat() method returns a new array from an array of nested arrays where all nested arrays are unwrapped and their elements are placed into a new array. The flat() method takes an optional argument to specify the depth of the nested arrays to unwrap.

[1, [2, 3]].flat(); // => [1, 2, 3]
[1, [2, [3]]].flat(); // => [1, 2, [3]]
[1, [2, [3]]].flat(2); // => [1, 2, 3]
[1, [2, [3]]].flat(3); // => [1, 2, 3]

The flatMap() method works like map() method except that the returned array is automatically flattened. The flatMap() method is more efficient than chaining a.map(f).flat() together.

let phrases = ['hello', 'world'];
let words = phrases.flatMap(phrase => phrase.split(' '));
words; // => ['hello', 'world']
[-2, -1, 1, 2].flatMap(x => x < 0 ? [] : Math.sqrt(x));
// => [1, 1.4142135623730951]

Adding arrays

The concat() method returns a new array that contains the elements of the original array on which it was invoked, followed by each of the arguments as array elements. If an argument is an array itself, then its elements are unwrapped and concatenated to the original array. The concat() method does not modify the original array.

let array = [1,2,3];
array.concat(4, 5); // => [1, 2, 3, 4, 5]
array.concat([4,5], [6,7]); // => [1, 2, 3, 4, 5, 6, 7]
array.concat([4], [5, [6,7]]); // => [1, 2, 3, 4, 5, [6, 7]]
array; // => [1,2,3]

Stacks and queues

The push() method appends one or more elements to the end of an array and returns the new length of the array. It does not flatten the array arguments. The pop() method deletes the last element of an array, decrements the array length, and returns the value that it removed. Both push() and pop() methods modify the array in place.

let stack = [];
stack.push(1,2); // => 2; stack === [1, 2]
stack.pop(); // => 2; stack === [1]
stack.push([4,5]); // => 2; stack === [1, [4, 5]]
stack.pop(); // => [4, 5]; stack === [1]
stack.pop(); // => 1; stack === []

The unshift() method adds one or more elements to the beginning of the array, shifts the existing array elements up to higher indices to make room, and returns the new length of the array. The shift() method removes the first element of the array and returns it, shifting all subsequent elements down one place.

let myStack = [];
myStack.unshift(1); // => 1; myStack == [1]
myStack.unshift(5); // => 2; myStack == [5, 1]
myStack.shift(); // => 5; myStack == [1]
myStack.shift(); // => 1; myStack == []
myStack.unshift(1,5); // => 2; myStack == [1, 5]

Subarray methods

The slice() method retrieves a subsection of the array from which it is invoked. Its first argument indicates the starting index, and the second argument denotes the end index, exclusive. Negative values for either argument reference array elements relative to the array's length. When only one argument is supplied, the subarray spans from the specified starting position to the array's end. Crucially, this operation leaves the original array unaltered.

let bricks = [1,2,3,4,5];
bricks.slice(0,3); // => [1, 2, 3]
bricks.slice(3); // => [4, 5]
bricks.slice(1,-1); // => [2, 3, 4]
bricks.slice(-3,-2); // => [3]

The splice() method inserts and deletes elements of an array. The first argument specifies the index to begin insertion or deletion. The second argument specifies the number of elements to delete from the array. Any further number of arguments specify the elements to be inserted into the array, starting from the position provided by the first argument. The original array is modified in place. The indices are appropriately re-arranged so that they remain contiguous with the rest of the array.

let list = [1,2,3,4,5,6,7,8,9];
list.splice(6); // => [7, 8, 9]; list === [1, 2, 3, 4, 5, 6]
list.splice(1,2); // => [2, 3]; list === [1, 4, 5, 6]
list.splice(2,0,'a','b'); // => []; list === [1, 4, 'a', 'b', 5, 6]
list.splice(2,2,[1,2],3); // => ['a', 'b']; list === [1, 4, [1, 2], 3, 5, 6]

The fill() method sets the subsection of an array to a specified value. The first argument is the value to set array elements to. The optional second argument specifies the starting index, and the optional third argument specifies the end index, exclusive. If the third argument is omitted, then the array is filled from the start index to the end of the array. The fill() method mutates the array it is invoked on, and also returns the modified array.

let integers = new Array(5);
integers.fill(0); // => [0, 0, 0, 0, 0]
integers.fill(9,1); // => [0, 9, 9, 9, 9]
integers.fill(8,2,-1); // => [0, 9, 8, 8, 9]

The copyWithin() method copies a slice of an array to a new position within the array. The first argument specifies the destination index to copy the sequence to. The second argument specifies the index from which to start copying elements (defaults to 0). The third argument specifies the index up to which to copy elements, excluding (defaults to the length of the array). This method modifies the array in place and returns the modified array, but does not change the length of the array.

let ints = [1,2,3,4,5];
ints.copyWithin(1); // => [1, 1, 2, 3, 4]
ints.copyWithin(2,3,5); // => [1, 1, 3, 4, 4]
ints.copyWithin(0, -2); // => [4, 4, 3, 4, 4]

Array searching and sorting

The indexOf() searches for an element with a specified value from the beginning of the array it is invoked upon and returns the index of the first found element, or -1 if it was not found. The optional second argument specifies the starting index at which to begin the search. The lastIndexOf() does the same except it searches from the end to the beginning of the array.

let seq = [0,1,2,1,0];
seq.indexOf(1); // => 1
seq.lastIndexOf(1); // => 3
seq.indexOf(3); // => -1
seq.indexOf(1,2); // => 3

The includes() method is designed to determine whether a specified value exists within an array, returning true if it does. Unlike the indexOf() and lastIndexOf() methods, includes() has the unique capability to detect the presence of the value NaN.

let sequence = [1, true, 3, NaN];
sequence.includes(true); // => true
sequence.includes(2); // => false
sequence.includes(NaN); // => true
sequence.indexOf(NaN); // => -1

The sort() method sorts the elements of an array in place and also returns the sorted array. When sort() is called with no arguments, it sorts the array elements in alphabetical order, casting values to strings temporarily. The undefined elements are sorted to the end of the array.

let fruits = ['guava', 'mango', 'apple'];
fruits.sort(); // => ['apple', 'guava', 'mango']

A comparison function can be passed as an argument to the sort() method for custom sorting. If the first argument should come first, the function should return a negative number. If the second argument should come first, the function should return a positive number. If the order is irrelevant, the function should return 0.

let orders = [33, 4, 1111, 222];
orders.sort(); // => [1111, 222, 33, 4]
orders.sort(function(a,b) {
  return a - b;
}); // => [4, 33, 222, 1111]
orders.sort((a,b) => b-a); // => [1111, 222, 33, 4]

The reverse() method reverses the order of the elements of an array in place and also returns the revered array.

let iseq = [1, 2, , null, undefined, NaN, 3];
iseq.reverse(); // => [3, NaN, undefined, null, empty, 2, 1]

Array to string conversions

The join() method concatenates the elements of the array together, separated by a specified separator string, and returns the resulting string. If no separator is provided, a comma is used by default.

let vegetables = ['potato', 'broccoli', 'eggplant'];
vegetables.join(); // => 'potato,broccoli,eggplant'
vegetables.join(' '); // => 'potato broccoli eggplant'
let empty10 = new Array(10);
empty10.join('-'); // => '---------'

The toString() method works the same as join() method with no arguments. The toLocaleString() method works like toString(), except that it calls the toLocaleString() method on each element, and then concatenates the result by using a locale-specific separator string.

[1, 2, 3].toString(); // => '1,2,3'
['a', 'b', 'c'].toString(); // => 'a,b,c'
[1, [2, 'c']].toString(); // => '1,2,c'
[1000, 2000, 3000].toLocaleString(); // => '1,000,2,000,3,000'

If you want to save the contents of an array in textual form for later use, serialize the array with JSON.stringify() method.

const points = [
  { x: 0, y: 0 },
  { x: 1, y: 1 }
];
points.join(", "); // => '[object Object], [object Object]'
JSON.stringify(points); // => '[{"x":0,"y":0},{"x":1,"y":1}]'

Static array functions

You can test whether a value is an array by calling the static method Array.isArray() on the unknown value.

Array.isArray({}); // => false
Array.isArray([]); // => true

In order to test for objects that work like arrays, use the following function.

function isArrayLike(o) {
  if (o &&
      typeof o === 'object' &&
      Number.isFinite(o.length) &&
      o.length >= 0 &&
      Number.isInteger(o.length) &&
      o.length < 4294967295) {
        return true;
      }
      return false;
}

Many JavaScript array methods are generic so that they work correctly when applied to array-like objects.

let arrObj = {'0': 'a', '1': 'b', length: 2};
Array.prototype.join.call(arrObj, '+'); // => 'a+b'
Array.prototype.map.call(arrObj, x => x.toUpperCase()); // => ['A', 'B']
Array.prototype.slice.call(arrObj, 0); // => ['a', 'b']
Array.from(arrObj); // => ['a', 'b']
Array.prototype.join.call('JavaScript', '-'); // => 'J-a-v-a-S-c-r-i-p-t'