JavaScript for impatient programmers (beta)
Please support this book: buy it or donate
(Ad, please don’t block.)

28. Arrays (Array)

28.1. The two roles of Arrays in JavaScript

Arrays play two roles in JavaScript:

In reality, there are also mixtures of these two roles.

Notably, Arrays-as-sequences are so flexible that you can use them as (traditional) arrays, stacks and queues (see exercise at the end of this chapter).

28.2. Basic Array operations

28.2.1. Arrays: creating, reading, writing

Create an Array:

const arr = ['a', 'b', 'c'];

Read an Array element (indices start at zero):

assert.equal(arr[0], 'a');

Change an Array element:

arr[0] = 'x';
assert.deepEqual(arr, ['x', 'b', 'c']);

The range of Array indices is 32 bits (excluding the maximum length): [0, 232−1)

28.2.2. Arrays: .length

Every Array has a property .length that can be used to both read and change(!) the number of elements in an Array.

The length of an Array is always the highest index plus one:

> const arr = ['a', 'b'];
> arr.length

If you write to the Array at the index of the length, you append an element:

> arr[arr.length] = 'c';
> arr
[ 'a', 'b', 'c' ]
> arr.length

Another way of (destructively) appending an element is via the Array method .push():

> arr.push('d');
> arr
[ 'a', 'b', 'c', 'd' ]

If you set .length, you are pruning the Array, by removing elements:

> arr.length = 1;
> arr
[ 'a' ]

  Exercise: Removing empty lines via .push()


28.2.3. Spreading into Arrays

Inside an Array literal, a spread element consists of three dots (...) followed by an expression. It results in the expression being evaluated and then iterated over. Each iterated value becomes an additional Array element. For example:

> const iterable = ['b', 'c'];
> ['a', ...iterable, 'd']
[ 'a', 'b', 'c', 'd' ]

Spreading is convenient for concatenating Arrays and other iterables into Arrays:

const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];

const concatenated = [...arr1, ...arr2, 'e'];
  ['a', 'b', 'c', 'd', 'e']);

28.2.4. Arrays: listing indices and entries

Method .keys() lists the indices of an Array:

const arr = ['a', 'b'];
  [...arr.keys()], // (A)
  [0, 1]);

.keys() returns an iterable. In line A, we spread to obtain an Array.

Listing Array indices is different from listing properties. When you do the latter, you get the indices – but as strings – plus non-index property keys:

const arr = ['a', 'b'];
arr.prop = true;

  ['0', '1', 'prop']);

Method .entries() lists the contents of an Array as [index, element] pairs:

const arr = ['a', 'b'];
  [[0, 'a'], [1, 'b']]);

28.2.5. Is a value an Array?

These are two ways of checking if a value is an Array:

> [] instanceof Array
> Array.isArray([])

instanceof is usually fine. You need Array.isArray() if a value may come from another realm. Roughly, a realm is an instance of JavaScript’s global scope. Some realms are isolated from each other (e.g. web workers in browsers), but there are also realms between which you can move data (e.g. same-origin iframes in browsers). x instanceof Array checks the prototype chain of x and therefore returns false if x is an Array from another realm.

typeof categorizes Arrays as objects:

> typeof []

28.3. for-of and Arrays

We have already encountered the for-of loop. This section briefly recaps how to use it for Arrays.

28.3.1. for-of: iterating over elements

The following for-of loop iterates over the elements of an Array.

for (const element of ['a', 'b']) {
// Output:
// 'a'
// 'b'

28.3.2. for-of: iterating over [index, element] pairs

The following for-of loop iterates over [index, element] pairs. Destructuring (described later), gives us convenient syntax for setting up index and element in the head of for-of.

for (const [index, element] of ['a', 'b'].entries()) {
  console.log(index, element);
// Output:
// 0, 'a'
// 1, 'b'

28.4. Array-like objects

Some operations that work with Arrays, require only the bare minimum: Values must only be Array-like. An Array-like value is an object with the following properties:

A TypeScript interface for ArrayLike looks as follows.

interface ArrayLike<T> {
  length: number;
  [n: number]: T;

Array.from() accepts Array-like objects and converts them to Arrays:

// If you omit .length, it is interpreted as 0

  Array.from({length:2, 0:'a', 1:'b'}),
  [ 'a', 'b' ]);

Array-like objects used to be common before ES6; now you don’t see them very often.

28.5. Converting iterable and Array-like values to Arrays

There are two common ways of converting iterable and Array-like values to Arrays: spreading and Array.from().

28.5.1. Converting iterables to Arrays via spreading (...)

Inside an Array literal, spreading via ... converts any iterable object into a series of Array elements. For example:

// Get an Array-like collection from a web browser’s DOM
const domCollection = document.querySelectorAll('a');

// Alas, the collection is missing many Array methods
assert.equal('map' in domCollection, false);

// Solution: convert it to an Array
const arr = [...domCollection];
assert.deepEqual( => x.href),
  ['', '']);

The conversion works, because the DOM collection is iterable.

28.5.2. Converting iterables and Array-like objects to Arrays via Array.from() (advanced)

Array.from() can be used in two modes. Mode 1 of Array.from(): converting

The first mode has the following type signature:

.from<T>(iterable: Iterable<T> | ArrayLike<T>): T[];

Interface Iterable is shown in the chapter that introduces iteration. Interface ArrayLike appeared earlier in this chapter.

With a single parameter, Array.from() converts anything iterable or Array-like to an Array:

> Array.from(new Set(['a', 'b']))
[ 'a', 'b' ]
> Array.from({length: 2, 0:'a', 1:'b'})
[ 'a', 'b' ] Mode 2 of Array.from(): converting and mapping

The second mode of Array.from() involves two parameters:

.from<T, U>(
  iterable: Iterable<T> | ArrayLike<T>,
  mapFunc: (v: T, i: number) => U,
  thisArg?: any)
  : U[];

In this mode, Array.from() does several things:

The optional parameter thisArg specifies a this for mapFunc.

That means that we are going from an iterable with elements of type T to an Array with elements of type U.

This is an example:

> Array.from(new Set(['a', 'b']), x => x + x)
[ 'aa', 'bb' ]

28.6. Creating and filling Arrays with arbitrary lengths

The best way of creating an Array is via an Array literal. However, you can’t use one if you don’t know the length the Array during development. Or if you want to keep the length flexible. Then I recommend the following techniques for creating and possibly filling Arrays:

If you are dealing with Arrays of integers or floats, consider Typed Arrays – which were created for this purpose.

28.7. Multidimensional Arrays

JavaScript does not have real multidimensional Arrays; you need to resort to Arrays whose elements are Arrays:

const DIM_X = 4;
const DIM_Y = 3;
const DIM_Z = 2;

const arr = [];
for (let x=0; x<DIM_X; x++) {
  arr[x] = []; // (A)
  for (let y=0; y<DIM_Y; y++) {
    arr[x][y] = []; // (B)
    for (let z=0; z<DIM_Z; z++) {
      arr[x][y][z] = 0; // (C)
arr[3][0][1] = 7;
assert.deepEqual(arr, [
  [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
  [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
  [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
  [ [ 0, 7 ], [ 0, 0 ], [ 0, 0 ] ],


28.8. More Array features (advanced)

In this section, we look at phenomena you don’t encounter that often when working with Arrays.

28.8.1. Array elements are (slightly special) properties

You’d think that Array elements are special, because you are accessing them via numbers. But the square brackets operator ([ ]) for doing so, is the same operator that is used for accessing properties. And, according to the language specification, it coerces any value (that is not a symbol) to a string. Therefore: Array elements are (almost) normal properties (line A) and it doesn’t matter if you use numbers or strings as indices (lines B and C):

const arr = ['a', 'b'];
arr.prop = 123;
  ['0', '1', 'prop']); // (A)

assert.equal(arr[0], 'a');  // (B)
assert.equal(arr['0'], 'a'); // (C)

To make matters even more confusing, this is only how the language specification defines things (the theory of JavaScript, if you will). Most JavaScript engines optimize under the hood and do use numbers (even integers) to access Array elements (the practice of JavaScript, if you will).

Property keys (strings!) that are used for Array elements are called indices. A string str is an index if converting it to a 32-bit unsigned integer and back results in the original value. Written as a formula:

ToString(ToUint32(key)) === key

JavaScript treats indices specially when listing property keys (of all objects!). They always come first and are sorted numerically, not lexicographically (where '10' would come before '2'):

const arr = 'abcdefghijk'.split('');
arr.prop = 123;
  ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'prop']);

Note that .length, .entries() and .keys() treat Array indices as numbers and ignore non-index properties:

const arr = ['a', 'b'];
arr.prop = true;

  Object.keys(arr), ['0', '1', 'prop']);

assert.equal(arr.length, 2);
  [...arr.keys()], [0, 1]);
  [...arr.entries()], [[0, 'a'], [1, 'b']]);

We used a spread element (...) to convert the iterables returned by .keys() and .entries() to Arrays.

28.8.2. Arrays are dictionaries and can have holes

JavaScript supports two kinds of Arrays:

In general, it’s best to avoid holes, because they make your code more complicated and are not handled consistently by Array methods. Additionally, JavaScript engines optimize dense Arrays, so they are faster.

You can create holes by skipping indices when assigning elements:

const arr = [];
arr[0] = 'a';
arr[2] = 'c';

assert.deepEqual(Object.keys(arr), ['0', '2']); // (A)

assert.equal(0 in arr, true); // element
assert.equal(1 in arr, false); // hole

In line A, we are using Object.keys(), because arr.keys() treats holes as if they were undefined elements and does not reveal them.

Another way of creating holes is to skip elements in Array literals:

const arr = ['a', , 'c'];

assert.deepEqual(Object.keys(arr), ['0', '2']);

And you can delete Array elements:

const arr = ['a', 'b', 'c'];
assert.deepEqual(Object.keys(arr), ['0', '1', '2']);
delete arr[1];
assert.deepEqual(Object.keys(arr), ['0', '2']);

For more information on how JavaScript handles holes in Arrays, consult “Exploring ES6”.

28.9. Adding and removing elements (destructively and non-destructively)

JavaScript’s Array is quite flexible and more like a combination of array, stack and queue. This section explores ways of adding and removing Array elements. Most operations can be performed both destructively (modifying the Array) and non-destructively (producing a modified copy).

28.9.1. Prepending elements and Arrays

In the following code, we destructively prepend single elements to arr1 and an Array to arr2:

const arr1 = ['a', 'b'];
arr1.unshift('x', 'y'); // prepend single elements
assert.deepEqual(arr1, ['x', 'y', 'a', 'b']);

const arr2 = ['a', 'b'];
arr2.unshift(...['x', 'y']); // prepend Array
assert.deepEqual(arr2, ['x', 'y', 'a', 'b']);

Spreading lets us unshift an Array into arr2.

Non-destructive prepending is done via spread elements:

const arr1 = ['a', 'b'];
  ['x', 'y', ...arr1], // prepend single elements
  ['x', 'y', 'a', 'b']);
assert.deepEqual(arr1, ['a', 'b']); // unchanged!

const arr2 = ['a', 'b'];
  [...['x', 'y'], ...arr2], // prepend Array
  ['x', 'y', 'a', 'b']);
assert.deepEqual(arr2, ['a', 'b']); // unchanged!

28.9.2. Appending elements and Arrays

In the following code, we destructively append single elements to arr1 and an Array to arr2:

const arr1 = ['a', 'b'];
arr1.push('x', 'y'); // append single elements
assert.deepEqual(arr1, ['a', 'b', 'x', 'y']);

const arr2 = ['a', 'b'];
arr2.push(...['x', 'y']); // append Array
assert.deepEqual(arr2, ['a', 'b', 'x', 'y']);

Spreading lets us push an Array into arr2.

Non-destructive appending is done via spread elements:

const arr1 = ['a', 'b'];
  [...arr1, 'x', 'y'], // append single elements
  ['a', 'b', 'x', 'y']);
assert.deepEqual(arr1, ['a', 'b']); // unchanged!

const arr2 = ['a', 'b'];
  [...arr2, ...['x', 'y']], // append Array
  ['a', 'b', 'x', 'y']);
assert.deepEqual(arr2, ['a', 'b']); // unchanged!

28.9.3. Removing elements

These are three destructive ways of removing Array elements:

// Destructively remove first element:
const arr1 = ['a', 'b', 'c'];
assert.equal(arr1.shift(), 'a');
assert.deepEqual(arr1, ['b', 'c']);

// Destructively remove last element:
const arr2 = ['a', 'b', 'c'];
assert.equal(arr2.pop(), 'c');
assert.deepEqual(arr2, ['a', 'b']);

// Remove one or more elements anywhere:
const arr3 = ['a', 'b', 'c'];
assert.deepEqual(arr3.splice(1, 1), ['b']);
assert.deepEqual(arr3, ['a', 'c']);

.splice() is covered in more detail in the quick reference section.

Destructuring via a rest element lets you non-destructively remove elements from the beginning of an Array (destructuring is covered later).

const arr1 = ['a', 'b', 'c'];
// Ignore first element, extract remaining elements
const [, ...arr2] = arr1;

assert.deepEqual(arr1, ['a', 'b', 'c']); // unchanged!
assert.deepEqual(arr2, ['b', 'c']);

Alas, a rest element must always come last in an Array. Therefore, you can only use it to extract suffixes.

  Exercise: Implementing a queue via an Array


28.10. Methods: iteration and transformation (.find(), .map(), .filter(), etc.)

In this section, we tak a look at Array methods for iterating over Arrays and for transforming Arrays. Before we do so, let’s consider two different approaches for iteration. It will help us understand how the methods work.

28.10.1. External iteration vs. internal iteration

Let’s assume that your code wants to iterate over the values “inside” an object. Two common approaches for making that possible, are:

28.10.2. Callbacks for iteration and transformation methods

Callbacks for iteration and transformation methods have signatures such as the following one.

callback: (value: T, index: number, array: Array<T>) => boolean

That is, the callback gets three parameters (it is free to ignore any of them):

What the callback is expected to return, depends on the iteration method: Sometimes it’s an arbitrary value, sometimes it’s a boolean.

28.10.3. Searching elements: .find(), .findIndex()

.find() returns the first element for which its callback returns a truthy value:

> [6, -5, 8].find(x => x < 0)
> [6, 5, 8].find(x => x < 0)

.findIndex() returns the index of the first element for which its callback returns a truthy value:

> [6, -5, 8].findIndex(x => x < 0)
> [6, 5, 8].findIndex(x => x < 0)

.findIndex() can be implemented as follows:

function findIndex(arr, callback) {
  for (const [i, x] of arr.entries()) {
    if (callback(x, i, arr)) {
      return i;
  return -1;

assert.equal(1, findIndex(['a', 'b', 'c'], x => x === 'b'));

28.10.4. .map(): copy while giving elements new values

.map() returns a copy of the receiver. The elements of the copy are the results of applying map’s callback parameter to the elements of the receiver.

All of this is easier to understand via examples:

> [1, 2, 3].map(x => x * 3)
[ 3, 6, 9 ]
> ['how', 'are', 'you'].map(str => str.toUpperCase())
[ 'HOW', 'ARE', 'YOU' ]
> [true, true, true].map((_, index) => index)
[ 0, 1, 2 ]

Note: _ is just another variable name.

.map() can be implemented as follows:

function map(arr, mapFunc) {
  const result = [];
  for (const [i, x] of arr.entries()) {
    result.push(mapFunc(x, i, arr));
  return result;

  map(['a', 'b', 'c'], (x, i) => `${i}.${x}`),
  ['0.a', '1.b', '2.c']);

  Exercise: Numbering lines via .map()


28.10.5. .filter(): only keep some of the elements

The Array method .filter() returns an Array collecting all elements for which the callback returns a truthy value.

For example:

> [-1, 2, 5, -7, 6].filter(x => x >= 0)
[ 2, 5, 6 ]
> ['a', 'b', 'c', 'd'].filter((_,i) => (i%2)===0)
[ 'a', 'c' ]

.filter() can be implemented as follows:

function filter(arr, filterFunc) {
  const result = [];
  for (const [i, x] of arr.entries()) {
    if (filterFunc(x, i, arr)) {
  return result;

  filter([ 1, 'a', 5, 4, 'x'], x => typeof x === 'number'),
  [1, 5, 4]);
  filter([ 1, 'a', 5, 4, 'x'], x => typeof x === 'string'),
  ['a', 'x']);

  Exercise: Removing empty lines via .filter()


28.10.6. .reduce(): deriving a value from an Array (advanced)

Method .reduce() is a powerful tool for computing a “summary” of an Array. A summary can be any kind of value:

This operation is also known as foldl (“fold left”) in functional programming and popular there. One caveat is that it can make code difficult to understand.

.reduce() has the following type signature (inside an Array<T>):

  callback: (accumulator: U, element: T, index: number, array: T[]) => U,
  init?: U)
  : U

T is the type of the Array elements, U is the type of the summary. The two may or may not be different.

To compute the summary of an Array arr, .reduce() feeds all Array elements to its callback, one at a time:

const accumulator_0 = callback(init, arr[0]);
const accumulator_1 = callback(accumulator_0, arr[1]);
const accumulator_2 = callback(accumulator_1, arr[2]);
// Etc.

callback combines the previously computed summary (stored in its parameter accumulator) with the current Array element and returns the next accumulator.

The result of .reduce() is the last result of callback, after it has visited all elements. In other words: callback does most of the work, .reduce() just invokes it in a useful manner.

Let’s look at an example of .reduce() in action: function addAll() computes the sum of all numbers in an Array arr.

function addAll(arr) {
  const startSum = 0;
  const callback = (sum, element) => sum + element;
  return arr.reduce(callback, startSum);
assert.equal(addAll([1,  2, 3]), 6); // (A)
assert.equal(addAll([7, -4, 2]), 5);

In this case, the accumulator holds the sum of all Array elements that callback has already visited.

How was the result 6 derived from the Array in line A? Via the following invocations of callback:

callback(0, 1) --> 1
callback(1, 2) --> 3
callback(3, 3) --> 6


Alternatively, we could have implemented addAll() via a for-of loop:

function addAll(arr) {
  let sum = 0;
  for (const element of arr) {
    sum = sum + element;
  return sum;

It’s hard to say which of the two implementations is “better”: The one based on .reduce() is a little more concise, while the one based on for-of may be a little easier to understand – especially if you are not familiar with functional programming. Example: finding indices via .reduce()

The following function is an implementation of the Array method .indexOf(). It returns the first index at which the given searchValue appears inside the Array arr:

const NOT_FOUND = -1;
function indexOf(arr, searchValue) {
  return arr.reduce(
    (result, elem, index) => {
      if (result !== NOT_FOUND) {
        // We have already found something: don’t change anything
        return result;
      else if (elem === searchValue) {
        return index;
      else {
        return NOT_FOUND;
assert.equal(indexOf(['a', 'b', 'c'], 'b'), 1);
assert.equal(indexOf(['a', 'b', 'c'], 'x'), -1);

One limitation of .reduce() is that you can’t finish early (in a for-of loop, you can break). Here, we don’t do anything once we have found what we were looking for. Example: doubling Array elements

Function double(arr) returns a copy of inArr whose elements are all multiplied by 2:

function double(inArr) {
  return inArr.reduce(
    (outArr, element) => {
      outArr.push(element * 2);
      return outArr;
  double([1, 2, 3]),
  [2, 4, 6]);

We modify the initial value [] by pushing into it. A non-destructive, more functional version of double() looks as follows:

function double(inArr) {
  return inArr.reduce(
    // Don’t change `outArr`, return a fresh Array
    (outArr, element) => [...outArr, element * 2],
  double([1, 2, 3]),
  [2, 4, 6]);

This version is more elegant, but also slower and uses more memory.

  Exercises: .reduce()

28.11. .sort(): sorting Arrays

.sort() has the following type definition:

sort(compareFunc?: (a: T, b: T) => number): this

.sort() always sorts string representations of the elements. These representations are compared via <. This operator compares lexicographically (the first characters are most significant). You can see that when comparing numbers:

> [200, 3, 10].sort()
[ 10, 200, 3 ]

When comparing human-language strings, you need to be aware that they are compared according to their code unit values (char codes):

> ['pie', 'cookie', 'éclair', 'Pie', 'Cookie', 'Éclair'].sort()
[ 'Cookie', 'Pie', 'cookie', 'pie', 'Éclair', 'éclair' ]

As you can see, all unaccented uppercase letters come before all unaccented lowercase letter, which come before all accented letters. Use Intl, the JavaScript internationalization API, if you want proper sorting for human languages.

Lastly, .sort() sorts in place: it changes and returns its receiver:

> const arr = ['a', 'c', 'b'];
> arr.sort() === arr
> arr
[ 'a', 'b', 'c' ]

28.11.1. Customizing the sort order

You can customize the sort order via the parameter, compareFunc, which returns a number that is:

Tip for remembering these rules: a negative number is less than zero (etc.).

28.11.2. Sorting numbers

You can use the following helper function to compare numbers:

function compareNumbers(a, b) {
  if (a < b) {
    return -1;
  } else if (a === b) {
    return 0;
  } else {
    return 1;
  [200, 3, 10].sort(compareNumbers),
  [3, 10, 200]);

The following is a quick and dirty alternative. Its downsides are that it is cryptic and that there is a risk of numeric overflow:

> [200, 3, 10].sort((a,b) => a-b)
[ 3, 10, 200 ]

28.11.3. Sorting objects

You also need to use a compare function if you want to sort objects. As an example, the following code shows how to sort objects by age.

const arr = [ {age: 200}, {age: 3}, {age: 10} ];
  arr.sort((obj1, obj2) => obj1.age - obj2.age),
  [{ age: 3 }, { age: 10 }, { age: 200 }] );

  Exercise: Sorting objects by name


28.12. Quick reference: Array<T>


28.12.1. new Array()

new Array(n) creates an Array of length n, that contains n holes:

// Trailing commas are always ignored.
// Therefore: number of commas = number of holes
assert.deepEqual(new Array(3), [,,,]);

new Array() creates an empty Array. However, I recommend to always use [], instead.

28.12.2. Static methods of Array

28.12.3. Methods of Array<T>.prototype

28.12.4. Sources


See quiz app.