|$ curl https://forge-ai.dev/api/markdown?path=docs/js/typed-arrays
$cat docs/javascript-—-typedarrays-&-buffers.md
updated This week·25 min read·published

JavaScript — TypedArrays & Buffers

JavaScriptTypedArraysAdvancedAdvanced
Introduction

Typed arrays provide a mechanism for reading and writing raw binary data in JavaScript. Unlike regular JavaScript arrays, typed arrays operate on a fixed-size ArrayBuffer — a contiguous block of memory — and interpret each byte as a specific numeric type (int8, uint16, float32, etc.).

They were introduced to enable WebGL, audio processing, WebSockets, file APIs, and other performance-critical binary protocols. Typed arrays bridge the gap between JavaScript and low-level systems programming, giving developers direct memory access with predictable performance characteristics.

This page covers ArrayBuffer, all typed array types, DataView for heterogeneous data, and practical use cases for binary data manipulation.

typed-intro.js
JavaScript
1// ArrayBuffer — raw binary memory
2const buffer = new ArrayBuffer(16); // 16 bytes
3console.log(buffer.byteLength); // 16
4
5// TypedArray — a view into the buffer
6const view = new Uint8Array(buffer);
7view[0] = 42;
8view[1] = 255;
9
10console.log(view[0]); // 42
11console.log(view[1]); // 255
12console.log(view.length); // 16 (elements)
13
14// Different typed array types interpret the same bytes differently
15const floatView = new Float32Array(buffer);
16console.log(floatView[0]); // 2.121995791e-314 (bytes interpreted as float)
17
18// DataView — read/write with explicit type and endianness
19const dataView = new DataView(buffer);
20dataView.setInt32(0, 12345, true); // little-endian
21console.log(dataView.getInt32(0, true)); // 12345
ArrayBuffer

ArrayBuffer is a fixed-length sequence of raw bytes. You cannot read or write directly to an ArrayBuffer — you must create a view (typed array or DataView) that interprets the bytes as specific types. ArrayBuffers can be transferred between contexts (workers) without copying via the Transferable interface.

MethodDescription
new ArrayBuffer(length)Creates a buffer of given byte length, initialized to 0
buffer.byteLengthReturns the length of the buffer in bytes
buffer.slice(start, end)Returns a new buffer containing bytes from start to end
ArrayBuffer.isView(value)Returns true if value is a TypedArray or DataView
buffer.transfer()Transfers ownership to a new buffer (detaches original)
arraybuffer.js
JavaScript
1// Creating and manipulating ArrayBuffers
2const buffer = new ArrayBuffer(8);
3console.log(buffer.byteLength); // 8
4
5// slice — create a view of a portion
6const slice = buffer.slice(2, 6);
7console.log(slice.byteLength); // 4
8
9// Transferable — zero-copy ownership transfer
10// Useful for transferring between workers without copying
11const transferable = new ArrayBuffer(1024);
12const transferred = transferable.transfer();
13console.log(transferable.byteLength); // 0 (detached!)
14console.log(transferred.byteLength); // 1024
15
16// SharedArrayBuffer — for shared memory concurrency
17// Requires cross-origin isolation headers
18const shared = new SharedArrayBuffer(1024);
19const sharedView = new Uint8Array(shared);
20// Can be accessed from multiple workers simultaneously
21// Use Atomics for synchronization
22
23// Resizable ArrayBuffers (ES2024+)
24const resizable = new ArrayBuffer(16, { maxByteLength: 1024 });
25console.log(resizable.resizable); // true
26resizable.resize(32);
27console.log(resizable.byteLength); // 32
28
29// Detached buffers
30const detachable = new ArrayBuffer(64);
31const buf2 = detachable.transfer();
32console.log(detachable.detached); // true
33// Any access to detachable throws TypeError

info

Use SharedArrayBuffer with Atomics for high-performance concurrent data access between workers. However, this requires the site to be cross-origin isolated (appropriate COOP and COEP headers). Without isolation, SharedArrayBuffer is unavailable.
TypedArray Types

JavaScript provides nine typed array constructors, each corresponding to a specific binary numeric format. All typed arrays share the same API — they differ only in element type, size, and range. Choosing the right type affects memory usage, precision, and performance.

TypeElement SizeRangeUse Case
Int8Array1 byte-128 to 127Small signed integers
Uint8Array1 byte0 to 255Bytes, raw data
Uint8ClampedArray1 byte0 to 255 (clamped)Canvas pixel data
Int16Array2 bytes-32768 to 32767Audio samples
Uint16Array2 bytes0 to 65535Unicode, pixel channels
Int32Array4 bytes-2^31 to 2^31-1Standard integers
Uint32Array4 bytes0 to 2^32-1Unsigned ints, flags
Float32Array4 bytes~1.2e-38 to 3.4e38Audio, WebGL, sensors
Float64Array8 bytes~5e-324 to 1.8e308High precision, scientific
BigInt64Array8 bytes-2^63 to 2^63-1Large integers
BigUint64Array8 bytes0 to 2^64-1Large unsigned ints
typed-array-types.js
JavaScript
1// All typed arrays share the same API
2const int8 = new Int8Array(4);
3int8[0] = 127; // max positive
4int8[1] = -128; // min negative
5int8[2] = 200; // wraps to -56 (overflow!)
6console.log(int8); // Int8Array [ 127, -128, -56, 0 ]
7
8// Uint8ClampedArray — clamps instead of wrapping
9const clamped = new Uint8ClampedArray(4);
10clamped[0] = -1; // clamps to 0
11clamped[1] = 999; // clamps to 255
12clamped[2] = 128;
13console.log(clamped); // Uint8ClampedArray [ 0, 255, 128, 0 ]
14
15// Float32 vs Float64 precision
16const f32 = new Float32Array(1);
17const f64 = new Float64Array(1);
18f32[0] = 0.1;
19f64[0] = 0.1;
20console.log(f32[0]); // 0.10000000149011612 (less precise)
21console.log(f64[0]); // 0.1 (more precise)
22
23// BigInt typed arrays
24const big = new BigInt64Array(2);
25big[0] = 9007199254740993n;
26big[1] = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
27console.log(big[0]); // 9007199254740993n
28console.log(big[1]); // 9007199254740993n
29
30// They all share the same prototype methods
31console.log(Int8Array.prototype.__proto__ === TypedArray.prototype);
32// true
33
34// .from() and .of() static methods
35const fromArray = Uint8Array.from([1, 2, 3]);
36const fromOf = Uint16Array.of(10, 20, 30);

best practice

Use Uint8Array for raw byte data (file reads, network protocols). Use Float32Array for audio and graphics (WebGL, WebAudio). Use Int32Array for standard integer arrays where performance matters. Avoid Float64Array unless you need the extra precision — it uses twice the memory and bandwidth.
DataView

DataView provides a low-level API for reading and writing multiple numeric types from an ArrayBuffer with explicit control over byte offset and endianness. Unlike typed arrays — which enforce a single element type across the entire buffer — DataView handles heterogeneous binary data.

dataview.js
JavaScript
1// DataView — flexible binary data access
2const buffer = new ArrayBuffer(16);
3const dv = new DataView(buffer);
4
5// Writing different types at different offsets
6dv.setInt8(0, -42);
7dv.setUint16(1, 30000, true); // little-endian
8dv.setFloat32(3, 3.14159, true);
9dv.setUint32(7, 0xDEADBEEF, false); // big-endian
10
11// Reading back
12console.log(dv.getInt8(0)); // -42
13console.log(dv.getUint16(1, true)); // 30000
14console.log(dv.getFloat32(3, true)); // 3.141590118408203
15console.log(dv.getUint32(7, false)); // 3735928495 (0xDEADBEEF)
16
17// DataView properties
18console.log(dv.buffer === buffer); // true
19console.log(dv.byteLength); // 16
20console.log(dv.byteOffset); // 0
21
22// DataView from a specific offset in a larger buffer
23const subBuffer = buffer.slice(4, 12);
24const subView = new DataView(subBuffer);
25console.log(subView.byteOffset); // 0
26console.log(subView.byteLength); // 8
27
28// Endianness matters
29const testBuf = new ArrayBuffer(4);
30const testView = new DataView(testBuf);
31testView.setUint32(0, 0x01020304, true); // little-endian
32console.log(testView.getUint8(0)); // 4 (LSB first)
33console.log(testView.getUint8(3)); // 1 (MSB last)
34
35testView.setUint32(0, 0x01020304, false); // big-endian
36console.log(testView.getUint8(0)); // 1 (MSB first)
37console.log(testView.getUint8(3)); // 4 (LSB last)

info

Always specify the endianness parameter (littleEndian) in DataView methods — the default is false (big-endian), but relying on defaults leads to bugs. Network protocols typically use big-endian, while most modern CPUs and file formats (WAV, BMP) use little-endian. Check the specification of whatever binary format you are parsing.
Binary Data Manipulation

Typed arrays excel at high-performance binary data processing. Common operations include encoding/decoding binary protocols, processing pixel data, audio waveform manipulation, and serialization. These operations benefit from the predictable memory layout and lack of boxing overhead.

String Encoding & Decoding

string-encoding.js
JavaScript
1// Convert strings to/from typed arrays
2function stringToBytes(str) {
3 // TextEncoder produces Uint8Array
4 return new TextEncoder().encode(str);
5}
6
7function bytesToString(bytes) {
8 return new TextDecoder().decode(bytes);
9}
10
11const utf8bytes = stringToBytes("Hello, 世界");
12console.log(utf8bytes); // Uint8Array [72, 101, 108, ...]
13
14const back = bytesToString(utf8bytes);
15console.log(back); // "Hello, 世界"
16
17// Manual UTF-8 encoding for low-level control
18function encodeUTF8(str) {
19 const buf = new ArrayBuffer(str.length * 4); // worst case
20 const view = new DataView(buf);
21 let offset = 0;
22
23 for (let i = 0; i < str.length; i++) {
24 let code = str.charCodeAt(i);
25
26 if (code < 0x80) {
27 view.setUint8(offset++, code);
28 } else if (code < 0x800) {
29 view.setUint8(offset++, 0xC0 | (code >> 6));
30 view.setUint8(offset++, 0x80 | (code & 0x3F));
31 } else if (code < 0xD800 || code >= 0xE000) {
32 view.setUint8(offset++, 0xE0 | (code >> 12));
33 view.setUint8(offset++, 0x80 | ((code >> 6) & 0x3F));
34 view.setUint8(offset++, 0x80 | (code & 0x3F));
35 } else {
36 // Surrogate pair
37 const codePoint = str.codePointAt(i);
38 view.setUint8(offset++, 0xF0 | (codePoint >> 18));
39 view.setUint8(offset++, 0x80 | ((codePoint >> 12) & 0x3F));
40 view.setUint8(offset++, 0x80 | ((codePoint >> 6) & 0x3F));
41 view.setUint8(offset++, 0x80 | (codePoint & 0x3F));
42 i++;
43 }
44 }
45
46 return new Uint8Array(buf.slice(0, offset));
47}

Binary Protocol Parsing

binary-protocol.js
JavaScript
1// Parsing a binary protocol header (e.g., TCP, file format)
2class BinaryHeader {
3 static from(buffer) {
4 const dv = new DataView(buffer);
5
6 return {
7 version: dv.getUint8(0),
8 flags: dv.getUint8(1),
9 length: dv.getUint32(2, false), // big-endian
10 checksum: dv.getUint16(6, false), // big-endian
11 timestamp: dv.getFloat64(8, true), // little-endian
12 reserved: new Uint8Array(buffer, 16, 8)
13 };
14 }
15
16 static create(version, flags, length, timestamp) {
17 const buffer = new ArrayBuffer(24);
18 const dv = new DataView(buffer);
19
20 dv.setUint8(0, version);
21 dv.setUint8(1, flags);
22 dv.setUint32(2, length, false);
23 dv.setUint16(6, 0, false); // checksum placeholder
24 dv.setFloat64(8, timestamp, true);
25 new Uint8Array(buffer, 16, 8).fill(0); // reserved
26
27 return buffer;
28 }
29}
30
31const header = BinaryHeader.create(1, 0x03, 1024, Date.now());
32const parsed = BinaryHeader.from(header);
33console.log(parsed.version); // 1
34console.log(parsed.flags); // 3
35console.log(parsed.length); // 1024

Image Pixel Manipulation

pixel-manipulation.js
JavaScript
1// Canvas pixel data manipulation with Uint8ClampedArray
2function grayscale(imageData) {
3 const pixels = imageData.data; // Uint8ClampedArray
4 for (let i = 0; i < pixels.length; i += 4) {
5 const r = pixels[i];
6 const g = pixels[i + 1];
7 const b = pixels[i + 2];
8 const gray = 0.299 * r + 0.587 * g + 0.114 * b;
9 pixels[i] = pixels[i + 1] = pixels[i + 2] = gray;
10 // alpha (i + 3) remains unchanged
11 }
12 return imageData;
13}
14
15// Efficient data copy between typed arrays
16const src = new Uint8Array([1, 2, 3, 4, 5]);
17const dst = new Uint8Array(5);
18dst.set(src); // copy entire array
19dst.set(src.subarray(0, 3), 2); // copy subarray at offset 2
20
21// Working with subarrays (no copy — shares buffer)
22const big = new Uint8Array(1000);
23const chunk = big.subarray(100, 200); // views same buffer
24console.log(chunk.buffer === big.buffer); // true
25console.log(chunk.byteOffset); // 100
26console.log(chunk.byteLength); // 100
27
28// Fill and copyWithin
29const arr = new Uint8Array(8);
30arr.fill(0xFF); // all bytes set to 255
31arr.copyWithin(2, 0, 2); // copy first 2 bytes to offset 2
32console.log(arr); // [255, 255, 255, 255, 0, 0, 0, 0]
33
34// Sorting typed arrays — in-place
35const scores = new Float64Array([3.2, 1.5, 4.8, 2.1]);
36scores.sort();
37console.log(scores); // [1.5, 2.1, 3.2, 4.8]
preview
Use Cases
WebGL graphics — vertex buffers, texture data, shader uniforms (Float32Array, Uint16Array)
Audio processing (WebAudio API) — PCM sample buffers, FFT data (Float32Array, Int16Array)
File I/O — reading binary file formats like PNG, WAV, ZIP, PDF via FileReader and TypedArrays
Network protocols — WebSocket binary frames, HTTP/2 frames, custom protocol parsers
Encryption & hashing — Web Crypto API uses ArrayBuffer for keys and digests
Image processing — canvas pixel manipulation with Uint8ClampedArray
Data serialization — efficient binary encoding (MessagePack, BSON, Protocol Buffers)
Scientific computing — large numeric datasets, matrix operations, simulations
Game development — asset loading, collision detection, state synchronization
WebAssembly — shared memory between JS and WASM modules via ArrayBuffer
🔥

pro tip

When reading binary files with FileReader, use readAsArrayBuffer instead of readAsDataURL or readAsText for binary formats. The ArrayBuffer result can be directly accessed with typed arrays or DataView, avoiding the overhead and potential data corruption of base64 encoding/decoding.
Best Practices
Pre-allocate buffers at the maximum expected size to avoid resizing overhead
Use subarray() instead of slice() when you need a window into existing data — slice copies, subarray does not
Use DataView for heterogeneous binary data, typed arrays for homogeneous numeric arrays
Always specify endianness in DataView calls — do not rely on defaults
Use Uint8Array for raw byte I/O — it is the most versatile and commonly supported type
Prefer TextEncoder/TextDecoder over manual encoding for standard string conversions
Be aware of buffer detachment — transferring a buffer detaches the original
Use SharedArrayBuffer with Atomics for concurrent access, not regular typed arrays
Typed arrays are 0-indexed and have fixed length — unlike regular arrays they cannot grow or shrink
Use Uint8ClampedArray for canvas pixel data — overflow wraps with Uint8Array but clamps with Uint8ClampedArray

best practice

A common pitfall: typeof typedArray === "object", not "typedarray". Use ArrayBuffer.isView(value) to check if a value is a typed array or DataView. Use value instanceof Uint8Array for specific type checks. Do not rely on constructor.name — it is not guaranteed in minified code.
$Blueprint — Engineering Documentation·Section ID: JS-TYPEDARRAYS·Revision: 1.0