Typed Arrays & ArrayBuffer
Most of the time you reach for a regular Array to hold values, but the web platform constantly moves raw bytes around: image pixels, audio samples, network packets, files, and GPU buffers. Typed arrays give JavaScript a way to read and write that binary data efficiently, with fixed numeric types and contiguous memory. They are the backbone of the Canvas, Fetch, WebSocket, Web Audio, and WebGL APIs.
ArrayBuffer: the raw memory
An ArrayBuffer is a fixed-length block of raw bytes. You cannot read or write it directly — it is just memory. To interact with it you create a view that interprets those bytes as numbers of a particular type.
const buffer = new ArrayBuffer(16); // 16 bytes of zeroed memory
console.log(buffer.byteLength);
Output:
16
Typed array views
A typed array is a view layered on top of an ArrayBuffer. The view decides how many bytes each element occupies and how to interpret them. Writing through one view changes the underlying buffer, which means other views see the same data.
const buffer = new ArrayBuffer(8);
const ints = new Int32Array(buffer); // two 32-bit integers
ints[0] = 256;
const bytes = new Uint8Array(buffer); // same buffer, byte view
console.log(bytes[0], bytes[1]); // little-endian layout
Output:
0 1
You can also construct a typed array directly from a length or an array of numbers, and it allocates its own buffer behind the scenes.
const samples = new Float32Array([0.5, -0.25, 1.0]);
console.log(samples.length, samples.buffer.byteLength);
Output:
3 12
The available view types
Each view enforces a numeric type, clamping or wrapping values that fall outside its range.
| View | Bytes / element | Value range |
|---|---|---|
Int8Array | 1 | -128 to 127 |
Uint8Array | 1 | 0 to 255 |
Uint8ClampedArray | 1 | 0 to 255 (clamps, no wrap) |
Int16Array | 2 | -32768 to 32767 |
Uint16Array | 2 | 0 to 65535 |
Int32Array | 4 | ±2.1 billion |
Uint32Array | 4 | 0 to ~4.3 billion |
Float32Array | 4 | 32-bit float |
Float64Array | 8 | 64-bit float |
BigInt64Array | 8 | 64-bit signed BigInt |
BigUint64Array | 8 | 64-bit unsigned BigInt |
Uint8ClampedArrayis special: assigning300stores255and-5stores0, instead of wrapping around. This is exactly what canvas pixel data needs, which is whyImageDatauses it.
How they differ from normal arrays
Typed arrays look array-like — they support map, filter, forEach, indexing, and length — but they are not regular arrays. The differences matter in practice.
| Feature | Regular Array | Typed array |
|---|---|---|
| Element types | any value | one fixed numeric type |
| Length | dynamic (grows/shrinks) | fixed at creation |
push / pop | yes | no |
Holes / undefined | possible | never (always numeric) |
| Memory | scattered objects | contiguous bytes |
const view = new Uint8Array(3);
view[0] = 42;
view[5] = 99; // out-of-bounds write is silently ignored
console.log(view.length, view[5]);
Output:
3 undefined
Because map on a typed array returns another typed array of the same kind, use Array.from when you need a plain array or a different element type.
const data = new Uint8Array([10, 20, 30]);
const doubled = data.map((n) => n * 2); // still a Uint8Array
const asArray = Array.from(data, (n) => n / 10);
console.log(doubled, asArray);
Output:
Uint8Array(3) [ 20, 40, 60 ] [ 1, 2, 3 ]
DataView for mixed, explicit layouts
When a buffer packs different types together — for example a binary file header — DataView lets you read each field at a specific byte offset and control endianness explicitly.
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setUint16(0, 513, false); // big-endian
view.setFloat32(2, 3.14, true); // little-endian
console.log(view.getUint16(0, false));
Output:
513
Canvas pixels: a real use case
The most common place front-end developers meet typed arrays is image manipulation. getImageData returns an ImageData object whose .data is a Uint8ClampedArray of RGBA bytes — four entries per pixel.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "tomato";
ctx.fillRect(0, 0, canvas.width, canvas.height);
const image = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = image.data; // Uint8ClampedArray, length = w * h * 4
// Invert every pixel: R, G, B (leave alpha untouched)
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 255 - pixels[i];
pixels[i + 1] = 255 - pixels[i + 1];
pixels[i + 2] = 255 - pixels[i + 2];
}
ctx.putImageData(image, 0, 0);
The same pattern powers WebGL (vertex and color buffers are Float32Array) and the Web Audio API (sample data is Float32Array).
Reading binary from the network
Typed arrays pair naturally with fetch. Calling .arrayBuffer() on a response gives you the raw bytes, ready to wrap in a view.
const res = await fetch("/logo.png");
const buffer = await res.arrayBuffer();
const bytes = new Uint8Array(buffer);
// PNG files start with the magic bytes 137 80 78 71
console.log(bytes.slice(0, 4).join(" "));
Output:
137 80 78 71
Best Practices
- Reach for typed arrays only when you genuinely handle binary data or need contiguous numeric memory; a plain
Arrayis simpler for everyday lists. - Pick the smallest view that fits your value range —
Uint8Arrayfor bytes,Float32Arrayfor GPU/audio — to save memory. - Use
Uint8ClampedArrayfor canvas pixels so values clamp to0–255instead of wrapping. - Remember the length is fixed: build a new buffer instead of trying to
push, and use.set()to copy chunks between views. - Use
DataViewwhen a buffer mixes types or when endianness matters; raw typed arrays follow the platform’s native byte order. - Convert with
Array.from(typedArray)when you need plain-array semantics or a non-numeric result.