JavaScript Sparse Arrays and Empty Slots Guide
A sparse array in JavaScript is an array containing “empty slots” or
holes where indices have no assigned value, resulting in an array length
greater than the actual number of elements. This article explains what
sparse arrays are, how empty slots differ from elements initialized to
undefined, and how JavaScript’s built-in iteration methods,
loops, and operators treat these holes during execution.
What is a Sparse Array?
A dense array has contiguous elements where every index from
0 to length - 1 contains a defined value. In
contrast, a sparse array contains uninitialized slots (often called
holes).
You can create a sparse array in several ways:
// Array constructor with a length
const arr1 = new Array(3); // [ <3 empty items> ]
// Array literals with omitted values
const arr2 = [1, , 3]; // [ 1, <1 empty item>, 3 ]
// Deleting an index
const arr3 = [1, 2, 3];
delete arr3[1]; // [ 1, <1 empty item>, 3 ]
// Setting an index beyond current length
const arr4 = [1];
arr4[3] = 4; // [ 1, <2 empty items>, 4 ]Empty Slots
vs. undefined
An empty slot is not the same as an element holding the value
undefined. An empty slot means the property key does not
exist on the array object at that index:
const sparse = [, ];
const dense = [undefined];
0 in sparse; // false (property does not exist)
0 in dense; // true (property exists with value undefined)
sparse.hasOwnProperty(0); // false
dense.hasOwnProperty(0); // trueHow Built-in Methods Treat Empty Slots
JavaScript handles empty slots inconsistently across different
ECMAScript specifications. Older methods (mostly ES5) tend to skip empty
slots entirely, while modern methods (ES6+) generally treat them as
undefined.
1. Methods That Skip Empty Slots
Most classic array transformation and testing methods check whether the index actually exists on the object before executing the callback. They skip empty slots entirely:
forEach(): The callback is never invoked for empty slots.filter(): Drops empty slots from the resulting array because the callback is not invoked for them.map(): Skips the callback on empty slots, but preserves the empty slots (holes) in the returned array.reduce()/reduceRight(): Ignores empty slots. If an initial value is not provided, the first non-empty slot is used as the initial accumulator.every()/some(): Ignores empty slots during condition checks.flat(): By default, removes all empty slots when flattening.
const sparse = [1, , 3];
sparse.forEach((val, idx) => console.log(idx, val));
// Output:
// 0 1
// 2 3
const mapped = sparse.map(x => x * 2);
console.log(mapped); // [ 2, <1 empty item>, 6 ]
const filtered = sparse.filter(() => true);
console.log(filtered); // [ 1, 3 ]2. Methods That
Treat Empty Slots as undefined
Modern ES6+ methods and iterators treat empty slots as if they hold
the value undefined:
find()andfindIndex(): Invoke the callback on empty slots withundefinedas the value argument.includes(): Matchesundefinedagainst empty slots.Array.from(): Converts empty slots into elements explicitly containingundefined.- Spread Operator (
[...arr]): Materializes empty slots intoundefined. for...ofloops: Iterates over empty slots, yieldingundefined.- Array Iterators (
keys(),values(),entries()): Yield indices andundefinedfor values on empty slots.
const sparse = [1, , 3];
console.log(sparse.find(x => x === undefined)); // undefined
console.log(sparse.findIndex(x => x === undefined)); // 1
console.log(sparse.includes(undefined)); // true
console.log([...sparse]); // [ 1, undefined, 3 ]
for (const val of sparse) {
console.log(val); // Logs 1, undefined, 3
}3. Special Cases and Conversions
indexOf()/lastIndexOf(): Unlikeincludes(), these methods skip empty slots and do not matchundefined. Searching forundefinedin a sparse array returns-1.join()/toString(): Treat empty slots identical toundefinedornull, converting them into empty strings.
const sparse = [1, , 3];
sparse.indexOf(undefined); // -1
sparse.join("-"); // "1--3"Summary
When working with JavaScript arrays, empty slots are missing indices
rather than initialized undefined values. Legacy iteration
methods (forEach, map, filter,
reduce) skip holes, whereas modern methods, iterator
protocols, and the spread operator normalize holes into
undefined.