Double Elements vs Tagged Pointers in JS Arrays

JavaScript engines optimize array storage in memory by categorizing arrays into different internal “element kinds” based on the data they contain. This article breaks down the fundamental differences between double elements and tagged pointers in JavaScript engines (such as Google V8), detailing how each format represents data, handles memory layout, and influences runtime performance.

Tagged Pointers in JavaScript Arrays

In modern JavaScript engines, values like Small Integers (Smis) and references to objects are represented using pointer tagging. Because memory addresses are aligned to word boundaries (typically 4 or 8 bytes), the least significant bits of a memory address are always zero. JavaScript engines exploit these unused bits as a “tag” to distinguish between immediate integer values and heap pointers without requiring extra memory.

When an array contains integers within a specific range (e.g., 31-bit signed integers) or references to JavaScript objects, it uses tagged pointer elements:

In V8, arrays containing only Smis use the PACKED_SMI_ELEMENTS kind, while arrays containing general objects use PACKED_ELEMENTS.

Double Elements in JavaScript Arrays

Floating-point numbers in JavaScript conform to the 64-bit IEEE 754 standard. Normally, numbers that cannot fit into the Smi representation must be allocated on the heap as HeapNumber objects, which introduces memory overhead and pointer indirection.

To optimize arrays of floating-point numbers, engines implement double elements (such as PACKED_DOUBLE_ELEMENTS in V8):

Core Differences

Feature Double Elements Tagged Pointers
Data Format Raw, unboxed 64-bit IEEE 754 floats Bit-tagged integer values or heap memory addresses
Heap Allocation None for individual floating-point values Required for values that cannot be represented as Smis
Element Kind (V8) PACKED_DOUBLE_ELEMENTS / HOLEY_DOUBLE_ELEMENTS PACKED_SMI_ELEMENTS or PACKED_ELEMENTS
Primary Use Case Arrays composed exclusively of non-integer numbers Arrays of integers, mixed types, objects, or strings

Transitions and Performance Implications

Array element kinds transition unidirectionally from the most specific to the most general:

  1. An array initialized with small integers starts as a tagged integer array (PACKED_SMI_ELEMENTS).
  2. Pushing a floating-point number (e.g., 4.5) causes the engine to reallocate and convert the array to a double array (PACKED_DOUBLE_ELEMENTS), unboxing the existing numbers into raw 64-bit floats.
  3. Pushing an object or string transitions the array to a general tagged pointer array (PACKED_ELEMENTS). In this state, any floating-point numbers must be boxed back into HeapNumber objects on the heap.

Double elements provide substantial performance gains for numerical computation by avoiding boxing, whereas tagged pointers provide the flexibility necessary to hold diverse object types and fast immediate integers in a single array structure.