Why arrays with “holes” behave weirdly, how they differ from undefined
, and what every developer should know before they bite you in production.

undefined
, and what every developer should know before they bite you in production.Introduction
Here’s a fun one:
const arr = [1, , 3];
console.log(arr.length); // 3
console.log(arr[1]); // undefined
console.log(1 in arr); // false 🤯
Wait, what? The array says it has length 3
, the second slot looks like undefined
, but the key 1
doesn’t even exist.
Welcome to sparse arrays — arrays with “holes” (missing indices). They look harmless, but they can lead to performance pitfalls, unexpected skips in iteration, and subtle bugs when merging, spreading, or serializing.
This article is your deep dive into sparse arrays: how they’re created, how they behave, why they exist, and how to avoid getting bitten by their quirks.
1. What Is a Sparse Array?
A sparse array is an array where some indices are missing — JavaScript just pretends those slots don’t exist.
const a = [1, , 3];
console.log(Object.keys(a)); // ['0', '2']
- Index
1
is missing—no property defined. - Array length is still
3
. - Hole ≠
undefined
. Holes mean “property not defined at that index.”
👉 Think of it like a bookshelf with an empty space where no book ever got placed.
2. How Sparse Arrays Are Created
A) Leaving gaps in literals
const arr = [1, , 3]; // hole at index 1
B) Setting a length larger than contents
const arr = [1, 2];
arr.length = 5;
console.log(arr); // [1, 2, <3 empty items>]
C) Deleting indices
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, <empty>, 3]
D) Using Array()
constructor
const arr = new Array(3);
console.log(arr); // [ <3 empty items> ]
3. Holes vs. undefined
This is the part that confuses devs most.
const a = [undefined];
const b = [ , ];
console.log(0 in a); // true → value explicitly undefined
console.log(0 in b); // false → hole, property doesn't exist
undefined
: A real value. Property exists, but its value isundefined
.- Hole: Property doesn’t exist at all.
Why it matters: Many array methods treat holes differently.
4. How Array Methods Treat Sparse Arrays
Iteration methods that skip holes
[1, , 3].forEach(x => console.log(x));
// logs 1, 3 (skips hole)
[1, , 3].map(x => x * 2);
// [2, <1 empty item>, 6]
[1, , 3].filter(Boolean);
// [1, 3] (hole skipped entirely)
Methods that treat holes as undefined
console.log([1, , 3].join('-')); // "1--3"
console.log([1, , 3].toString()); // "1,,3"
console.log([... [1, , 3]]);
// [1, undefined, 3] (spread fills hole with undefined)
console.log(Array.from([1, , 3]));
// [1, undefined, 3]
👉 Rule of thumb: Old-school array methods (map
, forEach
, filter
) skip holes; newer iteration protocols (for...of
, spread, Array.from
) treat holes as undefined
.
5. Performance Quirks
Sparse arrays are slower in most JS engines because they break array optimizations.
- Dense arrays: stored as a contiguous block in memory.
- Sparse arrays: stored more like dictionaries (key → value).
const dense = [1,2,3,4,5];
const sparse = [1, , , , 5];
// Loops on sparse arrays are typically slower
👉 Avoid sparsity if performance matters (large datasets, hot loops).
6. Real-World Gotchas
Gotcha 1: Copy methods behave differently
const arr = [1, , 3];
console.log([...arr]); // [1, undefined, 3]
Spread fills holes, so you might unintentionally introduce undefined
s.
Gotcha 2: JSON serialization
console.log(JSON.stringify([1, , 3]));
// "[1,null,3]"
JSON doesn’t support holes, so they become null
.
Gotcha 3: Object.keys
ignores holes
console.log(Object.keys([1, , 3])); // ["0","2"]
Missing indices don’t show up in enumeration.
Gotcha 4: in
operator
console.log(1 in [1, , 3]); // false
That slot truly doesn’t exist.
Gotcha 5: Spread vs. concat
const arr = [1, , 3];
console.log([].concat(arr)); // [1, , 3] → keeps hole
console.log([...arr]); // [1, undefined, 3] → fills hole
7. How to Handle Sparse Arrays Safely
A) Fill holes deliberately
const arr = [1, , 3];
const dense = Array.from(arr); // [1, undefined, 3]
B) Normalize with map
+ ??
const arr = [1, , 3];
const filled = arr.map(x => x ?? null); // [1, null, 3]
C) Avoid creating them in the first place
Prefer explicit undefined
or null
to holes.
// Bad
const arr = [1, , 3];
// Better
const arr = [1, undefined, 3];
D) Safely iterate with for...of
for (const x of [1, , 3]) {
console.log(x); // 1, undefined, 3
}
8. TypeScript & Linting Considerations
- TypeScript treats sparse arrays just like normal arrays, so holes sneak in.
- ESLint rule
no-sparse-arrays
flags them.
// ❌ ESLint: Unexpected comma in middle of array
const arr = [1, , 3];
👉 Enable this rule to catch accidental sparse arrays.
9. Practical Scenarios
Data ingestion (CSV/Excel)
Importing rows with missing columns often creates sparse arrays. Better: normalize them into null
or undefined
early.
UI rendering (React)
{[1, , 3].map(x => <div>{x}</div>)}
// Renders only 1 and 3. You might expect 2 divs with “undefined”.
API payloads
Sparse arrays serialized to JSON turn holes into null
, which can break schema validation.
Conclusion
Sparse arrays in JavaScript are arrays with holes — indices that don’t exist. They differ from undefined
values, they mess with iteration, and they often degrade performance.
- Iteration quirk: Some methods skip holes; others treat them as
undefined
. - Serialization quirk: Holes become
null
in JSON. - Performance quirk: Sparse arrays are slower.
- Best practice: Avoid them. Use
undefined
ornull
explicitly.
Pro Tip: Always enable ESLint’s no-sparse-arrays
to catch them before they sneak into production.
Call to Action (CTA)
Have sparse arrays ever tripped you up?
Drop your war stories (and fixes) in the comments. And share this post with a teammate who still thinks [1, , 3]
is the same as [1, undefined, 3].
Leave a Reply