Skip to main content

Command Palette

Search for a command to run...

Under the Hood of JavaScript Arrays: Optimization & V8 Debugging 🔍

Updated
5 min read

JavaScript arrays look simple on the surface, but under the hood, they are highly optimized structures. By exploring them using V8 Debug Engine (d8) with jsvu, we can peek into how JavaScript executes and optimizes arrays for performance.

In this post, I’ll walk you through array types, optimizations, and practical experiments in the V8 debug environment.

/🔧 Setting Up V8 Debug Engine

jsvu (JavaScript Version Updater) makes it easy to download different JavaScript engines, including V8 Debug builds.

npm install -g jsvu
jsvu --engines=v8-debug

After installation, you can run the V8 Debug shell (d8):

& "C:\Users\Dell\.jsvu\bin\v8-debug.cmd"

Inside the d8> shell, you can execute JavaScript code and even inspect internal engine structures with intrinsics like %DebugPrint().

d8> %DebugPrint()
undefined
d8> %DebugPrint(arr)
DebugPrint: 0x27b00749efd: [JSArray]
 - map: 0x027b000673a1 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x027b000673c9 <JSArray[0]>
 - elements: 0x027b000007bd <FixedArray[0]> [PACKED_SMI_ELEMENTS]
 - length: 0
 - properties: 0x027b000007bd <FixedArray[0]>
 - All own properties (excluding elements): {
    0x27b00000df1: [String] in ReadOnlySpace: #length: 0x027b0071d1cd <AccessorInfo name= 0x027b00000df1 <String[6]: #length>, data= 0x027b00000011 <undefined>> (const accessor descriptor, attrs: [W__]), location: descriptor
 }
0x27b000673a1: [Map] in OldSpace
 - map: 0x027b0005f1f1 <MetaMap (0x027b0005f241 <NativeContext[300]>)>
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - unused property fields: 0
 - elements kind: PACKED_SMI_ELEMENTS
 - enum length: invalid
 - back pointer: 0x027b00000011 <undefined>
 - prototype_validity_cell: 0x027b00000ac9 <Cell value= [cleared]>
 - instance descriptors #1: 0x027b000679e5 <DescriptorArray[1]>
 - transitions #1: 0x027b00067a05 <TransitionArray[5]>
   Transitions #1:
     0x027b00000e8d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x027b00067a21 <Map[16](HOLEY_SMI_ELEMENTS)>
 - prototype: 0x027b000673c9 <JSArray[0]>
 - constructor: 0x027b000672f1 <JSFunction Array (sfi = 0000027B0072282D)>
 - dependent code: 0x027b000007cd <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0

[]

🔹 Types of JavaScript Arrays in V8

V8 classifies arrays based on element types and layout in memory. Broadly, arrays fall into two categories:

1️⃣ Continuous Arrays (Packed)

These arrays store elements contiguously in memory, which makes access and iteration fast. Continuous arrays are further divided into three types:

  • SMI Elements – Small integers (SMI = Small Integer). Highly optimized for speed.

  • Packed Elements – Arrays containing objects, strings, or functions. Can store multiple data types.

  • Double Elements – Arrays containing floating-point numbers (floats).

const arr1 = [1, 2, 3];       // Packed SMI Elements
const arr2 = [1, 2, 3.5];     // Packed Double Elements
const arr3 = [1, '2', {}];    // Packed Elements

2️⃣ Holey Arrays

These arrays contain gaps (missing elements). V8 calls them Holey_Elements, and they are less optimized than packed arrays.

const arr = [1, 2,, 4, 5]; // Holey array with a missing element at index 2

Examples with code

// Array optimization example in V8

const arrTwo = [1, 2, 3, 4, 5];
// This is a continuous array of small integers (SMI elements)
// V8 calls this a "Packed_SMI_Elements" array because all elements are small integers stored contiguously in memory.

const arrThree = [1, 2,, 3, 4, 5];
// This array contains a missing element (a "hole") at index 2
// V8 treats this as a "Holey_Elements" array, which is less optimized than packed arrays.

arrTwo.push(6.0);
// Adding a floating-point number converts the array from "Packed_SMI_Elements" to "Packed_Double_Elements"
// Once converted to Packed_Double_Elements, the array **cannot revert back** to Packed_SMI_Elements, even if you remove the float later.
// This is because V8 maintains type stability for performance reasons.

arrTwo.push('7');
// Adding a string converts the array to a general "Packed_Elements" array
// Packed_Elements can store multiple data types (numbers, strings, objects).

arrTwo[10] = 11;
// Assigning a value to an index that creates a gap makes the array "Holey_Elements" again
// Holey arrays are less efficient for iteration and element access because of gaps in memory.

console.log(arrTwo);
// Output shows the array with holes and mixed types

console.log(arrTwo.length);
// The length reflects the highest index plus one (in this case, 11)

console.log(arrTwo[19]);
// Accessing an index that does not exist returns undefined
// This lookup is expensive in V8 because it performs a bound check and may traverse the array's prototype chain.

Why the value/ output is coming as undefined and it is a normal undefined so what’s the matter here

 // 🔹 Bound Check Example
// hasOwnProperty checks whether an object has a specific property.
// Example: 
// hasOwnProperty(arrTwo, 9)
// hasOwnProperty(arrTwo.prototype, 9)
// hasOwnProperty(Object.prototype, 9)

// Why this is expensive:
// In JavaScript, when you access an array index or property, the engine checks:
// 1. If the element exists in the array itself.
// 2. If not, it checks the array’s prototype.
// 3. Then it checks Object.prototype.
// This full depth traversal makes bound checks costly, especially for sparse arrays or deep prototype chains.

const arrFour = [1, 2, 3, 4, 5];
console.log(arrFour[2]); // Direct access, fast because it’s a packed SMI array

// 🔹 Array Types Hierarchy
// Continuous arrays: SMI (small integers) > Double (floats) > Packed (mixed types)
// Holey arrays: H_SMI > H_Double > H_Packed
// "H" indicates Holey arrays, which contain gaps or missing indices.

// Example of Holey SMI Array
const arrFive = new Array(3); // Creates 3 empty slots → Holey_SMI_Elements
arrFive[0] = '1'; // Holey_Elements
arrFive[0] = '2'; // Still Holey_Elements
arrFive[0] = '3'; // Still Holey_Elements

// Example of Packed Elements Array
const arr5 = [];
arr5.push('1'); // Packed_Elements
arr5.push('2'); // Packed_Elements
arr5.push('3'); // Packed_Elements
// Packed arrays are contiguous and optimized for iteration

// Adding NaN converts a packed SMI array to Packed_Double
const arrSix = [1, 2, 3, 4, 5];
arrSix.push(NaN); 
// Array type becomes Packed_Double_Elements
// Even though it’s a float placeholder, V8 converts the array to double internally for performance

// 🔹 Iteration
// Arrays can be iterated using:
// - for loops
// - for-of loops
// - forEach
// Iteration on Holey arrays or arrays with mixed types is slower compared to Packed SMI or Packed Double arrays

✅ Key Concepts Illustrated in This Code

  1. Bound Checks Are Expensive

    • Accessing missing indices triggers prototype chain lookups, which slows down operations on arrays.
  2. Continuous vs Holey Arrays

    • Continuous arrays (Packed) are contiguous in memory → fast access.

    • Holey arrays have gaps → slower access due to additional checks.

  3. SMI → Double → Packed

    • Arrays of small integers (SMI) are most optimized.

    • Adding floats converts them to Double.

    • Adding strings or mixed types converts them to Packed.

Once converted, arrays do not revert back to previous types.

The learning I took from a youtube channel CHAI AUR CODE

Here is the link:-
ChaiaurCode:- Hitesh Choudhary

I hope you find this helpful