This post builds upon holey JavaScript arrays and shows what happens when manipulating arrays with holes. Some array methods ignore holes, some transform them and some preserve them. This inconsistent processing can cause hard-to-find bugs – do not pour precious hours down array holes.
What happens when you call some operations on holey arrays?
let holeyArray = [1, , 2];
holeyArray.map(console.dir);
https://twitter.com/abdulapopoola/status/1318206429040275457
console.diris better thanconsole.logfor examiningJSONobjects.
That is odd, right? Where did the hole go?
JavaScript’s array methods can be grouped into three categories:
- The skippers: methods that skip holes
- The preservers: methods that preserve holes
- The transformers: methods that remove holes or convert them into other values.
Array Methods that skip holes
forEach
[1, , 2].forEach(console.dir);
// Outputs only 1 and 2
every
let count = 0;
[1, , 2].every(function(x) {
count++;
return true;
});
console.log(count); // 2 instead of 3
[1, , 2].every(console.dir);
// Outputs only 1 and 2
some
let count = 0;
[1, , 2].every(function(x) {
count++;
return true;
});
console.log(count); // 2 instead of 3
[1, , 2].every(console.dir);
// Outputs only 1 and 2
This pattern of checking the count is starting to get boring. Can we write a helper function that takes in the array method, runs the check and returns true or false? Yes! JavaScript supports functional programming and we can use call to do this.
Helper Function
function skipsArrayHoles(arrayMethod) {
let count = 0;
let holeyArray = [1, ,2];
let checkerFxn = function (x) {
count++;
return false;
};
arrayMethod.call(holeyArray, checkerFxn);
return count < holeyArray.length;
}
How does the Helper function work?
The skipsArrayHoles helper accepts an array method as a parameter. The input method is then invoked on an array of known length and a counter tracks invocation counts. The closure on counter allows it to be modified as the original array method is invoked.
If every element (including holes) are processed, then the counter will be equal to the length of the array. Otherwise, it’ll be less.
Read more about partial application and binding.
Why does CheckerFxn return false?
Array methods like some will short-circuit and return immediately a true response is received. To ensure a complete walk of the entire array, a negative result (i.e. false) is needed.
Let’s continue using the new method.
filter
skipsArrayHoles([].filter); // true
reduce
skipsArrayHoles([].reduce); // true
map
skipsArrayHoles(map); // true
Array Methods that preserve holes
[].keys and [].values
The array [].keys method and its counterpart [].values preserve holes.
let sparseArray = [1, ,2];
console.log([...[].keys(sparseArray)]); //[0,1,2]
console.log([...[].values(sparseArray)]); //[0,undefined,2]
Note
In yet another befuddling quirk, Object.keys and Object.values do not preserve holes.
let sparseArray = [1, ,2];
console.log(Object.keys(sparseArray)); //["0", ,"2"]
console.log(Object.values(sparseArray)); //[1,2]
reverse
reverse preserves holes.
let holeyArray = [1, , 2];
holeyArray.reverse();
// [2, , 1];
Array Methods that transform holes
There are a special class of array methods that do not walk through all the array elements. Let’s see how these methods handle holes.
join
The join method will convert holes to empty strings. The same treatment is meted out to undefined and null values.
let holeyArray = [1, , 2, null, 3, undefined];
let delimiter = '|';
holeyArray.join(delimiter);
// "1||2||3|"
sort
sort does not remove holes. The default sort will return null values before undefined and finally holes.
let holeyArray = [1, , 2, null, 3, undefined];
let delimiter = '|';
holeyArray.sort();
// [1, 2, 3, null, undefined, empty]
toString
Like join, toString converts holes to undefined. This is similar to how toString converts both undefined and null to empty strings.
let holeyArray = [1, , 2, null, 3, undefined];
holeyArray.toString();
// "1,,2,,3,"
flat
flat removes holes.
let holeyArray = [1, , 2];
holeyArray.flat();
// [1, 2];
fill
The fill method will insert values into all array indexes (including holes). This is the reason for the common idiom of filling up an array to avoid empty slots.
let filledArray = new Array(3).fill('1');
Why should you care?
Array holes can lead to time-draining bugs.
It is easy to pour down countless debugging hours down the hole (pun intended). Imagine trying to figure out the reason why an array returns a smaller result set even though it seems to contain undefined values (those are holes that look like undefined).
Avoid such by:
- Filling arrays at declaration time
- Avoid writing beyond the length of an array
Summarized table
| Method | Hole Handling Strategy |
|---|---|
| forEach | Skips |
| every | Skips |
| some | Skips |
| filter | Skips |
| join | Transforms |
| sort | Preserves |
| map | Skips |
| reduce | Skips |
| flat | Removes |
| keys | Preserves |
| pop | Transforms |
| reverse | Preserves |
| toString | Transforms |
| values | Preserves |
Hopefully, this exposed you to some of the holey behaviour in JavaScript.
Related
Discover more from CodeKraft
Subscribe to get the latest posts sent to your email.

Very Insightful. Thanks for Sharing!
LikeLike