JavaScriptTyped Arrays

Typed Arrays

Regular JavaScript arrays are flexible: any element can be any type, the length can grow or shrink, and the engine handles the memory for you. That flexibility costs memory and disables a lot of optimisations. Typed arrays are the escape hatch — fixed-size, fixed-element-type buffers of raw binary data. You will not need them for most application code, but the moment you touch a binary protocol, audio sample, image pixel, or WebGL vertex, they become essential.

Two layers: ArrayBuffer and views

The model is two-tier:

  • An ArrayBuffer is a fixed-length block of raw bytes. You cannot read or write it directly — it is just memory.

  • A typed array view (like Uint8Array, Float32Array) is a window onto an ArrayBuffer that interprets the bytes as a particular numeric type.

  • Multiple views can share the same buffer — useful for parsing binary formats where one segment is bytes and another is 32-bit integers.

JS
const buf = new ArrayBuffer(16);   // 16 raw bytes

const bytes = new Uint8Array(buf);      // 16 elements: each 0-255
const ints  = new Int32Array(buf);      // 4 elements: each a 32-bit signed int

bytes.length;   // 16
ints.length;    // 4   (16 bytes / 4 bytes-per-int)

bytes[0] = 255;
ints[0];        // depends on endianness — see below
The typed array family

Each view enforces a specific element type and size. They all share the same array-like API: length, indexing, forEach, map, set, subarray.

  • Int8Array / Uint8Array / Uint8ClampedArray — 1 byte per element. Clamped saturates writes outside 0-255 instead of wrapping.

  • Int16Array / Uint16Array — 2 bytes per element.

  • Int32Array / Uint32Array — 4 bytes per element.

  • Float32Array — 4-byte IEEE-754 single-precision floats.

  • Float64Array — 8-byte IEEE-754 double-precision floats. Same precision as regular JS numbers.

  • BigInt64Array / BigUint64Array — 8 bytes per element, hold BigInt values.

Creating typed arrays

JS
// From a length (zero-filled)
const a = new Int32Array(5);
// Int32Array(5) [0, 0, 0, 0, 0]

// From an iterable
const b = new Uint8Array([10, 20, 30]);

// From another typed array (copies and converts)
const c = new Float32Array(b);
// Float32Array(3) [10, 20, 30]

// From an existing buffer
const buf = new ArrayBuffer(8);
const d = new Int16Array(buf);       // 4 elements
const e = new Int16Array(buf, 2, 2); // start at byte 2, take 2 elements
Typed arrays cannot grow
Once created, a typed array's length is fixed. There is no `push` or `pop`. To resize, allocate a new one and copy the bytes. Modern environments also expose `ArrayBuffer.transfer` for the same effect.
Out-of-range writes — wrap, clamp, truncate

Each view has its own rules for values that do not fit. They never throw — they coerce.

JS
const u8  = new Uint8Array(1);
u8[0] = 300;          // wraps: 300 % 256 = 44

const u8c = new Uint8ClampedArray(1);
u8c[0] = 300;         // clamped to 255
u8c[0] = -5;          // clamped to 0

const f32 = new Float32Array(1);
f32[0] = 0.1 + 0.2;   // stored as nearest 32-bit float, loses precision
Clamped is for image pixels
`Uint8ClampedArray` exists because canvas pixel data needs values in 0-255 with no wraparound. A red pixel that just slightly overshoots should stay red (255), not turn black (0).
Endianness with DataView

Typed array views read and write using the host machine's byte order — little-endian on almost every modern CPU. When you are parsing a binary protocol with a fixed byte order, use DataView instead. It lets you specify endianness for every read and write.

JS
const buf = new ArrayBuffer(4);
const view = new DataView(buf);

view.setUint32(0, 0x01020304, false); // big-endian write
new Uint8Array(buf);                  // [1, 2, 3, 4]

view.setUint32(0, 0x01020304, true);  // little-endian write
new Uint8Array(buf);                  // [4, 3, 2, 1]

view.getUint16(0, true);              // read 2 bytes, little-endian
When to actually use typed arrays
  • Binary protocols — parsing custom file formats, network packets, MIDI data.

  • WebGL and Canvas — vertex buffers, pixel data (getImageData().data is a Uint8ClampedArray).

  • Web AudioAudioBuffer.getChannelData() returns a Float32Array of samples.

  • Cryptocrypto.subtle and getRandomValues take and return typed arrays.

  • WebAssembly — sharing memory between JS and Wasm via Memory.buffer.

  • Performance-critical numerics — a Float64Array of a million elements uses less memory and is faster to iterate than a regular array of a million numbers.

A small example: parsing a header

Read a 12-byte header

JS
// Imagine a binary header:
//   bytes 0-3 : magic number (uint32, big-endian)
//   bytes 4-5 : version (uint16, big-endian)
//   bytes 6-7 : flags  (uint16, big-endian)
//   bytes 8-11: payload length (uint32, big-endian)

function parseHeader(buffer) {
  const view = new DataView(buffer);
  return {
    magic:   view.getUint32(0, false).toString(16),
    version: view.getUint16(4, false),
    flags:   view.getUint16(6, false),
    length:  view.getUint32(8, false),
  };
}
What you still get from regular arrays

Typed arrays have most array methods (map, filter, forEach, reduce, slice) but with one twist: methods that return a new array return a typed array of the same kind. Operations that would change the length (splice, pop) do not exist. If you need a flexible list of numbers, stick with a regular array; reach for a typed array when shape and memory layout actually matter.