Dense and sparse arrays
A dense array is an array in which every index from 0 to length - 1 is occupied, while a sparse array is one where some indexes are missing, that is, it has holes. The difference is not cosmetic: it decides which elements the iteration methods can see and how fast the engine works with the array.
Theory
TL;DR
- Dense array: every index from
0tolength - 1really exists. - Sparse array: some indexes are missing, and
lengthis larger than the number of real elements. - A hole is not the value
undefined: the property with that key simply does not exist, soi in arrgivesfalse. - Holes appear in three ways:
delete, a skipped item in the literal[1, , 3], and manually increasinglength. forEach,map,filter,reduceandObject.keysskip holes, whilefor,for...of, spread andArray.fromsee them asundefined.- Sparse arrays are slower, so instead of holes it is better to store an explicit
nullorundefined.
Quick example
const dense = [1, 2, 3];
const sparse = [1, , 3];
dense.forEach(v => console.log(v)); // 1, 2, 3
sparse.forEach(v => console.log(v)); // 1, 3 (index 1 is skipped)
console.log(Object.keys(dense)); // ['0', '1', '2']
console.log(Object.keys(sparse)); // ['0', '2']Both arrays have length === 3, but the second one has only two real elements.
Definitions
A dense array is an array in which all indexes are occupied by elements, from 0 to length - 1.
That is:
- there are no skipped indexes (no holes),
- every index really exists in the object.
const dense = [10, 20, 30];Here:
- the indexes are
0, 1, 2 dense.length === 30 in denseistrue,1 in denseistrue,2 in denseistrue
A sparse array is an array in which some indexes are missing, so it contains holes.
const sparse = [10, , 30];
console.log(sparse); // [10, <1 empty item>, 30]Here:
- index
1does not exist (1 in sparseisfalse) sparse.length === 3, but there are really only two elements
How holes appear
-
By using
delete:javascriptconst arr = [1, 2, 3]; delete arr[1]; console.log(arr); // [1, <1 empty item>, 3] -
By skipping an item in a literal:
javascriptconst arr = [1, , 3]; -
By increasing
lengthmanually:javascriptconst arr = [1, 2]; arr.length = 5; console.log(arr); // [1, 2, <3 empty items>] -
By writing to a far index:
javascriptconst arr = [1]; arr[4] = 5; console.log(arr); // [1, <3 empty items>, 5]
Worth knowing separately: new Array(3) also creates a sparse array of three holes, while Array.from({ length: 3 }) gives a dense array of three undefined values.
Behaviour during iteration
| Method | Dense array | Sparse array |
|---|---|---|
for | walks every index | walks every index, a hole reads as undefined |
for...of | walks every value | walks every position, a hole reads as undefined |
forEach() | visits every element | skips the empty ones |
map() | processes every element | skips the empty ones but keeps the holes in the result |
filter(), reduce(), some(), every() | process every element | skip the empty ones |
in | checks that the index exists | returns false for holes |
Object.keys() | lists every index | leaves the empty ones out |
spread [...arr], Array.from() | copies the values | turns holes into undefined |
join(), toString() | the usual result | a hole becomes an empty string |
The main trap is that the very same hole "does not exist" for some methods and becomes undefined for others.
const sparse = [1, , 3];
console.log(sparse.map(v => v * 2)); // [2, <1 empty item>, 6]
console.log([...sparse]); // [1, undefined, 3]
console.log(sparse.join('-')); // '1--3'Impact on performance
Sparse arrays:
- run slower,
- are optimised worse by the V8 engine, because it moves the array from the fast elements representation into a dictionary one,
- and often behave oddly during iteration.
That is why in real projects it is better to avoid arrays with holes and use
nullorundefinedas explicit "empty" values.
If a sparse array has already appeared, it is easy to make it dense again:
const sparse = [1, , 3];
const filled = Array.from(sparse); // [1, undefined, 3]
const cleaned = sparse.filter(() => true); // [1, 3], the holes are goneIn short
| Array type | Description | Example | Behaviour |
|---|---|---|---|
| Dense | All indexes are occupied | [1, 2, 3] | Predictable, fast |
| Sparse | Some indexes are missing | [1, , 3] | Methods skip the empty ones, slower |
Common mistakes
- Thinking that a hole is
undefined. There is no value at all; check withi in arrorObject.hasOwn(arr, i)rather than comparing againstundefined. - Trusting
lengthas the element count. In a sparse arraylengthis bigger than the real number of elements; count withObject.keys(arr).length. - Creating an array with
new Array(n)and then iterating it.new Array(3).map(...)does nothing, because every position is a hole; useArray.from({ length: 3 }, (_, i) => i). - Expecting all methods to behave the same.
map()skips holes, while spread andfor...ofturn them intoundefined. - Using
deleteto remove elements. That is the most common way sparse arrays appear in production code; usesplice()instead. - Increasing
lengthmanually to "reserve space". This does not speed anything up, it just makes the array sparse.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.