Categories
JavaScript JavaScript Basics

What are Holes in Arrays?

One special feature of JavaScript arrays is that not every slot in the array has to be filled with values. This means that we can skip values as follows:

let arr = [];  
arr[1] = 1;  
arr[10] = 10;

We don’t have to worry about setting values for other slots of the array.

There’s also an alternative syntax for doing the above, by writing:

[, 1, , 2];

What we have above are called holes of an array, where we have nothing between the commas. An array with holes is called a sparse array.

In this piece, we’ll look at how holes in arrays are handled in JavaScript.


Checking for Values in Arrays

We can check for holes in arrays with the in operator. To do this, we can write something like the following code:

const arr = [, 1, , 2];  
1 in arr;

We should get true returned from the last line since 1 is in the array.

ES6 treat holes in arrays as undefined entries. So if we want to check for holes in the array, check for undefined.

Iterators and generators also treat holes as undefined. For example, if we have

const arr = [, 1, , 2];  
const iter = arr[Symbol.iterator]()

for (let a of iter) {  
  console.log(a);  
}

we get

undefined  
1  
undefined  
2

If we call next to get the next item, as follows,

iter.next();

we get

{value: undefined, done: false}

for the first entry.

Likewise, if we have the given generator,

function* generator () {  
  const arr = [, 1, , 2];  
  for (let a of arr) {  
    yield a;  
  }  
}

for (let a of generator()) {  
  console.log(a);  
}

we get the same thing.


Array.from()

Array.from() treats holes as undefined like with iterators and generators.

For example, if we have

const arr = [, 1, , 2];  
const arrFrom = Array.from(arr);

then we get that the value of arrFrom is

[undefined, 1, undefined, 2]

Likewise, if we create an array from array-like objects, where we have non-negative integers as keys and a length property with a non-negative number as a value, missing entries are also treated as undefined.

For example, if we run

const arrFrom = Array.from({  
  1: 'foo',  
  length: 2  
});

we get the value of arrFrom as

[undefined, "foo"]

How Array.prototype Methods Treat Holes

The behavior of these methods differs with different versions of JavaScript. In ES5, they’re the following:

  • forEach, filter, every, and some ignore holes.
  • map skips but preserves holes.
  • join and toString treat holes as if they were undefined elements but treat both null and undefined as empty strings.

The following is a full list of methods and how they deal with holes. Each method acts differently.

  • concat — keeps holes
  • copyWithin — holes are copied
  • entries, keys, values — treats holes as undefined
  • every — ignores holes
  • fill — fills holes
  • filter — removes holes
  • find — treats holes as elements
  • findIndex — treats holes as elements
  • forEach — ignores holes
  • indexOf — ignores holes
  • join — converts holes to empty strings
  • lastIndexOf — ignores holes
  • map — preserves holes
  • pop — treat holes as elements
  • push — preserves holes
  • reduce , reduceRight— ignores holes
  • reverse — preserves holes
  • shift — treat holes as undefined
  • slice — preserves holes
  • sort — preserves holes
  • toString — preserves holes
  • unshift — preserves holes
  • values — converts holes to undefined

When arrays are sparse, they have holes. They’re iterated through as undefined, but each array prototype method treats holes differently, so we have to be careful when defining arrays with holes and dealing with them.

We can use the in operator to check if a given entry is in an array.

Array.from() also treats holes as undefined and turns holes into undefined when converting arrays or array-like objects with holes into arrays.