Array Internals: Packed, Holey & 6 Element Kinds
Ek float daalo, poora array slow — yeh V8 ka one-way transition hai, wapas nahi jaate.
V8 tracks the type of elements in an array to optimize memory layout and access speed. It categorizes arrays into 6 element kinds: PACKED_SMI_ELEMENTS, PACKED_DOUBLE_ELEMENTS, PACKED_ELEMENTS, and their HOLEY counterparts.
SMI (Small Integer): 31-bit integers stored directly in memory. Fastest possible array type — values fit in CPU registers.
DOUBLE: 64-bit floating point numbers. When you add a single float to a SMI array, the entire array is converted to DOUBLE storage.
ELEMENTS: References to objects, strings, or other values. When you add an object to a SMI or DOUBLE array, it converts to ELEMENTS storage.
// SMI Array (fastest - integers only)
const smiArr = [1, 2, 3]; // PACKED_SMI_ELEMENTS
// DOUBLE Array (float added, downgrade triggered)
smiArr.push(1.5); // Now PACKED_DOUBLE_ELEMENTS (entire array converted!)
// OBJECT Array (object added, downgrade triggered)
smiArr.push({ x: 1 }); // Now PACKED_ELEMENTS (slowest of the 3)
// The transition is PERMANENT for this array instance
// Even if you remove the float and object, it stays PACKED_ELEMENTS
PACKED: Contiguous memory, no empty slots. Every index has a value. V8 can do direct pointer arithmetic for access.
HOLEY: Has gaps (empty slots). Created by: missing indices, delete arr[i], or Array(n).
V8 must perform a "hole check" on every access in a HOLEY array. Accessing a hole returns undefined, but V8 has to check the prototype chain first — a slow operation.
Creating holes is one of the easiest ways to ruin array performance.
// PACKED Array (fast)
const packed = [1, 2, 3, 4]; // PACKED_SMI_ELEMENTS
// HOLEY Array (slow - has gaps)
const holey = [1, , , 4]; // HOLEY_SMI_ELEMENTS
console.log(holey[1]); // undefined
// V8 check: Is index 1 a hole? Yes - walk prototype chain - return undefined.
// The delete operator creates holes
const arr = [1, 2, 3];
delete arr[1]; // arr is now [1, , 3] (HOLEY_SMI_ELEMENTS)
// NEVER use delete on arrays! Use splice() instead.
// Array constructor creates holes
const sparse = new Array(3); // [empty x 3] (HOLEY_SMI_ELEMENTS)
sparse.push(1); // [empty x 3, 1]If an array becomes extremely sparse (lots of holes), V8 switches to Dictionary Mode.
Dictionary mode uses a hash table instead of a contiguous block of memory. Property access becomes O(n) hash lookup instead of O(1) pointer arithmetic.
Triggered by: very large gaps, deleting indices frequently.
Array.isArray() still returns true, but internally it behaves nothing like a fast array.
// Triggering Dictionary Mode
const arr = [];
arr[100000] = 'way out there'; // Huge gap = sparse array
// V8 decides: allocating 100,000 empty slots wastes memory
// Instead, it creates a hash table internally
// The performance difference
const fastArr = [1, 2, 3, 4, 5];
const slowArr = [];
slowArr[50000] = 1; // Dictionary mode triggered
console.time('fast');
for (let i = 0; i < fastArr.length; i++) { fastArr[i]; }
console.timeEnd('fast');
console.time('slow');
for (let i = 0; i < slowArr.length; i++) { slowArr[i]; } // Iterates 50k empty slots!
console.timeEnd('slow');
Map or Object, not an Array. Arrays are optimized for contiguous, dense data. Using them as hash tables defeats V8's optimizations entirely.How array methods handle holes is inconsistent and surprising.
map, filter, forEach SKIP holes entirely — the callback is never called for holes.
join and toString treat holes as empty strings.
Array.from and spread [...arr] convert holes to undefined (filling them in).
Array.from(arrayLike) creates a PACKED array — a good way to "heal" a holey array.
const holey = [1, , 3];
// map SKIPS holes
holey.map(x => x * 2); // [2, empty, 6]
// forEach SKIPS holes
holey.forEach(x => console.log(x)); // logs 1, 3 (skips the hole!)
// join treats holes as empty string
holey.join('-'); // "1--3"
// Spread and Array.from FILL holes with undefined
const packedCopy = [...holey]; // [1, undefined, 3]
const fromCopy = Array.from(holey); // [1, undefined, 3]
// Healing a holey array
const healed = Array.from(holey); // Now PACKED_ELEMENTS (contains undefined)
healed.map(x => x * 2); // [2, NaN, 6] (callback runs for undefined now!)Array length is the highest numeric index + 1, NOT the count of values.
Setting arr.length = 0 is a fast way to clear an array (truncates it).
Setting arr.length = 10 on a shorter array creates holes.
length is writable and configurable, but usually you shouldn't modify it manually.
const arr = [];
arr[10] = 'end';
console.log(arr.length); // 11 (10 + 1)
console.log(arr); // [empty x 10, 'end']
// Truncating an array
const data = [1, 2, 3, 4, 5];
data.length = 2;
console.log(data); // [1, 2] (rest are gone forever)
// Extending creates holes
data.length = 5;
console.log(data); // [1, 2, empty x 3]
// Fast clear trick
const cache = [1, 2, 3];
cache.length = 0; // Empties the array, but keeps the reference
arr.length = 0 is the fastest way to empty an array if you need to keep the same reference (e.g., other variables point to this array). It removes all elements without allocating a new array object.Lo kar liya — Key Points:
- ✅ V8 categorizes arrays into 6 element kinds: PACKED_SMI, PACKED_DOUBLE, PACKED_ELEMENTS, and their HOLEY versions
- ✅ Transitions are one-way: SMI → DOUBLE → OBJECT. Adding a float to an integer array permanently downgrades it
- ✅ Holey arrays require V8 to perform "hole checks" on every access, degrading performance
- ✅ Extremely sparse arrays trigger Dictionary Mode, using a hash table instead of contiguous memory (very slow)
- ✅ Array methods like map, filter, and forEach skip holes entirely; spread and Array.from convert holes to undefined
- ✅ Array length is the highest index + 1, not the count of elements; modifying length can truncate or create holes
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login