cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

ArrayBuffer & typed arrays

Learn JavaScript typed arrays from bytes to ArrayBuffer, DataView, endian order, files, pixels, transfer, and resizable memory.

By the end, you can
  • 01
    Model bytes and buffersExplain bytes, hex notation, ArrayBuffer raw memory, and how several views can reinterpret the same bytes.
  • 02
    Choose the right viewPick integer, float, BigInt, clamped, or DataView APIs based on range, rounding, endian order, and file layout.
  • 03
    Move binary data safelyUse subarray, slice, set, resizable buffers, transfer, fetch/Blob arrayBuffer, and postMessage transfer lists deliberately.

Bytes before arrays

The previous lesson finished the Regular expressions module with regex performance. This first lesson in Binary data & memory switches from text patterns to bytes: file headers, pixels, audio samples, WebGL buffers, compressed data, and protocol packets.

A bit is a single 0 or 1. A byte is eight bits, so it can hold 256 different patterns. When we read a byte as an unsigned number, those patterns are 0 through 255. Hexadecimal is a compact way to write those patterns: 0xff means 255, 0x00 means 0, and 0x89 0x50 0x4e 0x47 is easier to scan than decimal bytes in a file signature.

One byte in decimal, hex and bitsPop out in the code editor (opens in a new tab)JavaScript
const byte = 0b10101010;console.log(byte);console.log(byte.toString(16).padStart(2, "0"));console.log(byte.toString(2).padStart(8, "0"));
Plain definition

An ArrayBuffer is a fixed-size or resizable block of raw memory. A typed array or DataView is a view that gives meaning to those bytes: unsigned byte, signed 16-bit integer, 32-bit float, 64-bit BigInt, and so on.

Normal arrays from Array basics can grow, hold mixed values, and store references. Typed arrays are dense and numeric. They are how JavaScript talks to browser and platform APIs that already think in bytes. The value rules build on Number & Math in depth and BigInt, but the storage is much closer to the metal.

ArrayBuffer and views over the same memory

STEP THROUGH

You cannot use buffer[0] to read an ArrayBuffer. It deliberately has no element type. Create a view, then the same bytes become meaningful. Multiple views can sit on the same buffer, and a write through one view is immediately visible through the others.

Real-life analogyA buffer is a blank warehouse shelf

The shelf exists before anyone decides what is stored on it. A byte view labels every slot separately. A 16-bit view labels pairs. A DataView is a clipboard where you choose the offset and format for each read.

In real life: A shelf with eight empty slots
In JavaScript: new ArrayBuffer(8)
In real life: A label that says each slot is one small box
In JavaScript: new Uint8Array(buffer)
In real life: A label that groups slots in pairs
In JavaScript: new Uint16Array(buffer)
In real life: A clipboard that reads any offset
In JavaScript: new DataView(buffer)

Where the analogy stops: Real memory is managed by the engine and may move internally. The shelf model is for understanding views, not physical addresses.

Watch three views share one buffer
Step 0 of 11Ready
Your turn: follow the blue line

Step through one ArrayBuffer shared by several views. Writes through one view immediately change what the others see.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const bytes = new Uint8Array(buffer);bytes.set([0x34, 0x12, 0, 0]);const words = new Uint16Array(buffer);console.log(words[0].toString(16));words[1] = 0x7fff;console.log(Array.from(bytes.slice(2, 4)).join(" "));const view = new DataView(buffer);view.setInt8(4, -1);console.log(bytes[4]);console.log(ArrayBuffer.isView(view));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The step-through uses Uint16Array to show shared memory, so the byte order for multi-byte typed-array elements follows the platform. Today that is almost always little-endian. When a file format or protocol says exactly which byte comes first, use DataView and pass the endian flag explicitly.

Byte-grid visualizer
One buffer, several interpretationsPop out in the code editor (opens in a new tab)JavaScript
const buffer = new ArrayBuffer(8);const bytes = new Uint8Array(buffer);bytes.set([0x78, 0x56, 0x34, 0x12, 0xff, 0x00, 0x80, 0x3f]);const view = new DataView(buffer);console.log(view.getUint32(0, true).toString(16));console.log(view.getUint32(0, false).toString(16));console.log(new Float32Array(buffer, 4, 1)[0]);
Live bytes8 bytes
Uint8Array bytes78 56 34 12 ff 00 80 3f
getUint32(0, true)0x12345678
getUint32(0, false)0x78563412
getInt16(0, true)22136
getInt16(0, false)30806
Float32Array byte 41.0000303983688354
Try it yourself

Bytes 0–3 are 78 56 34 12. DataView reads them as little-endian 305419896 and big-endian 2018915346.

Edit one byte and every interpretation updates, because all views read the same eight bytes. Values are clamped to the byte range 0–255.

The typed array family: ranges, overflow and rounding

Every typed array constructor chooses three things: element size, value type, and conversion behavior. Integer typed arrays store a fixed number of bits. When a Number does not fit, ordinary integer typed arrays wrap modulo the element size. Floating arrays round to their precision. Uint8ClampedArray is the exception designed for pixels: it clamps into 0–255 instead of wrapping.

Typed array constructors at a glance
ConstructorBytesValue kindRange / precisionCommon uses
Int8Array1Signed integer-128 to 127Small deltas, audio samples, protocol fields
Uint8Array1Unsigned integer0 to 255Bytes from files, network packets, crypto, WASM memory
Uint8ClampedArray1Unsigned clamped integer0 to 255, rounded and clampedCanvas ImageData RGBA pixels
Int16Array / Uint16Array2Signed / unsigned integer-32768 to 32767 / 0 to 65535PCM audio, UTF-16-ish units, binary formats
Int32Array / Uint32Array4Signed / unsigned integer32-bit rangesIndexes, bit masks, WebGL/WebGPU data
Float16Array2Half-precision floatAbout 3 decimal digits, finite max 65504GPU, ML, image and geometry data when supported
Float32Array4Single-precision floatAbout 7 decimal digitsWebGL positions, audio buffers, binary scientific data
Float64Array8Double-precision floatSame format as JavaScript numberHigh precision numeric arrays
BigInt64Array / BigUint64Array8Signed / unsigned BigInt64-bit integer rangesLarge IDs, counters, file offsets, nanosecond timestamps
Choose the binary-data tool
  • Read PNG width at byte offset 16.
  • Edit canvas RGBA channels.
  • Upload x/y/z positions to WebGL.
  • Move a 10 MB buffer to a worker without copying.
  • Scan every incoming packet byte.
  • Store exact unsigned 64-bit IDs.
Try it yourself
0 of 6 correct

Sort each job by the first view or memory move you would reach for.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Real conversion outputsPop out in the code editor (opens in a new tab)JavaScript
const signed = new Int8Array([127, 128, 129, -129]);const unsigned = new Uint8Array([255, 256, 257, -1]);const pixels = new Uint8ClampedArray([-20, 12.5, 13.5, 255.4, 300]);const floats = new Float32Array([Math.PI, 1 / 3, 1e40]);const bigs = new BigInt64Array([1n, -2n]);console.log(Array.from(signed).join(","));console.log(Array.from(unsigned).join(","));console.log(Array.from(pixels).join(","));console.log(Array.from(floats).join(","));console.log(Array.from(bigs).join(","));
What the conversion example proves
InputPrinted outputReason
new Int8Array([127, 128, 129, -129])127,-128,-127,127Integers wrap modulo 256, then read as signed two's-complement values.
new Uint8Array([255, 256, 257, -1])255,0,1,255Unsigned integers also wrap modulo 256.
new Uint8ClampedArray([-20, 12.5, 13.5, 255.4, 300])0,12,14,255,255Canvas pixels clamp to 0–255 and round to the nearest integer, with .5 ties to even.
new Float32Array([Math.PI, 1 / 3, 1e40])3.1415927410125732,0.3333333432674408,InfinityFloat32 rounds to 32-bit precision and overflows to Infinity.
new BigInt64Array([1n, -2n])1,-2BigInt typed arrays require BigInt values such as 1n; Number values throw a TypeError.
BigInt arrays are not Number arrays

BigInt64Array and BigUint64Array store 64-bit integer elements and require BigInt values such as 1n. Passing 1 as a Number throws a TypeError. Use them when the binary format really contains 64-bit integers and the value must stay exact.

Float16Array is new

Float16Array is the ES2025 half-precision float view. MDN currently marks it Baseline 2025: Chrome/Edge 135, Firefox 129, Safari 18.2, and Node.js 24+. It is not in Node 22, so production code should feature-detect before constructing it. MDN Baseline 2025: Chrome/Edge 135, Firefox 129, Safari 18.2, Node.js 24+. Node 22 does not have Float16Array.

Feature-detect Float16ArrayPop out in the code editor (opens in a new tab)JavaScript
const Float16 = globalThis.Float16Array;if (typeof Float16 === "function") {  const values = new Float16([1, 1 / 3, 65504, 1e-8]);  console.log(Array.from(values).join(","));} else {  console.log("Float16Array not supported here");}

DataView and endianness

BYTE ORDER

Endianness answers: when a number uses several bytes, which byte comes first? Big-endian writes the most significant byte first. Little-endian writes the least significant byte first. CPU and typed-array native order is almost always little-endian today, but binary formats are not all the same.

DataView is the safest parser for structured binary data because every operation names a byte offset and, for multi-byte values, a little-endian boolean. Omit the boolean or pass false for big-endian. Pass true for little-endian.

Read 0x12345678 in both byte orders
Step 0 of 6Ready
Your turn: follow the blue line

Write one 32-bit value with DataView, then read those same four bytes both ways.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const view = new DataView(buffer);view.setUint32(0, 0x12345678, false);console.log(Array.from(new Uint8Array(buffer)).map((byte) => byte.toString(16).padStart(2, "0")).join(" "));console.log(view.getUint32(0, false).toString(16));console.log(view.getUint32(0, true).toString(16));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
The same DataView example as plain codePop out in the code editor (opens in a new tab)JavaScript
const buffer = new ArrayBuffer(4);const view = new DataView(buffer);view.setUint32(0, 0x12345678, false);console.log(Array.from(new Uint8Array(buffer)).map((byte) => byte.toString(16).padStart(2, "0")).join(" "));console.log(view.getUint32(0, false).toString(16));console.log(view.getUint32(0, true).toString(16));
Rule of thumb

If the bytes come from your own in-memory numeric array, native typed arrays are convenient. If the bytes come from a file, network protocol, or documented binary format, parse with DataView so the byte order is visible in code review.

subarray, slice, set and ArrayBuffer.isView

Typed arrays look array-like, but the memory rules matter. subarray(start, end) creates a new view into the same buffer. slice(start, end) copies bytes into a new typed array. set(source, offset) bulk-copies values into an existing typed array, and ArrayBuffer.isView(value) tells you whether a value is a typed array or a DataView.

Sharing versus copyingPop out in the code editor (opens in a new tab)JavaScript
const bytes = new Uint8Array([1, 2, 3, 4]);const shared = bytes.subarray(1, 3);const copied = bytes.slice(1, 3);shared[0] = 99;console.log(bytes.join(","));console.log(copied.join(","));bytes.set([7, 8], 2);console.log(bytes.join(","));console.log(ArrayBuffer.isView(shared));console.log(ArrayBuffer.isView(bytes.buffer));
Memory operations you will use constantly
OperationCopies bytes?What changes when the source changes?
view.subarray(1, 3)NoThe returned view sees later writes to the same bytes.
view.slice(1, 3)YesThe returned typed array keeps its own copy.
view.set([7, 8], 2)Copies into existing storageThe target buffer changes at the destination offset.
ArrayBuffer.isView(value)NoReturns true for typed arrays and DataView, false for ArrayBuffer.

Real files, pixels, WebGL and audio buffers

Binary APIs usually start by giving you an ArrayBuffer. Files and blobs use Blob.arrayBuffer(). fetch() responses have arrayBuffer(). Canvas ImageData stores pixels in a Uint8ClampedArray. WebGL, WebGPU, Web Audio, compression libraries, and WebAssembly all use typed arrays because they are dense, predictable, and cheap to hand to native code.

Where typed arrays appear in real front-end work
Use caseHow bytes arriveTyped-array move
File headersRead Blob.arrayBuffer() or File.slice(...).arrayBuffer(), then use DataView at documented offsets.PNG stores width and height as big-endian 32-bit integers in the IHDR chunk.
Canvas pixelsImageData.data is a Uint8ClampedArray of RGBA bytes.Use the canvas lesson when you need to edit pixels or understand tainted canvases.
Networking and storagefetch(url).then(r => r.arrayBuffer()), Cache API, IndexedDB, and streams all hand you byte chunks.Keep a Uint8Array view for byte-wise parsing.
WebGL, WebGPU and audioGraphics and audio APIs expect dense numeric buffers such as Float32Array and Uint16Array.Typed arrays avoid per-element object overhead and copy cleanly to native code.

PNG files start with an eight-byte signature. The IHDR chunk stores width at byte offset 16 and height at byte offset 20, both as big-endian unsigned 32-bit integers. That makes it a perfect DataView example.

Parse a PNG header from bytesPop out in the code editor (opens in a new tab)JavaScript
const png = new Uint8Array([  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,  0x00, 0x00, 0x00, 0x0d,  0x49, 0x48, 0x44, 0x52,  0x00, 0x00, 0x03, 0x20,  0x00, 0x00, 0x02, 0x58,]);const view = new DataView(png.buffer, png.byteOffset, png.byteLength);const signatureOk = png.slice(0, 8).join(" ") === "137 80 78 71 13 10 26 10";const width = view.getUint32(16, false);const height = view.getUint32(20, false);console.log(signatureOk);console.log(width + "x" + height);
The same idea with a real File objectJavaScript
async function readPngSize(file) {  const header = new Uint8Array(await file.slice(0, 24).arrayBuffer());  const view = new DataView(header.buffer, header.byteOffset, header.byteLength);  const signature = header.slice(0, 8).join(" ");  if (signature !== "137 80 78 71 13 10 26 10") throw new Error("Not a PNG");  return {    width: view.getUint32(16, false),    height: view.getUint32(20, false),  };}
Workers and shared memory

SharedArrayBuffer lets multiple agents see the same bytes, and Atomics provides safe synchronization operations. That is a concurrency topic, not a beginner byte topic; connect it to Messaging & cloning and Web Workers when you need parallel work.

Resizable and transferable buffers

Modern ArrayBuffer can be created with maxByteLength. If supported, resize(newByteLength) changes the current size up to that maximum. Views with an explicit length are fixed-length; views created without a length can track the buffer as it grows and shrinks.

Resizable and transferable buffer vocabulary
IdeaCode shapeWhat it means
Resizable buffernew ArrayBuffer(4, { maxByteLength: 8 })The buffer can grow or shrink up to the maximum, if the engine supports resizable ArrayBuffer.
Fixed-length viewnew Uint8Array(buffer, 0, 4)The view keeps its requested length while the range exists; if the buffer shrinks below it, it becomes out-of-bounds.
Length-tracking viewnew Uint8Array(buffer)When no length is supplied on a resizable buffer, the view tracks the current buffer length.
Transferbuffer.transfer()Moves bytes into a new buffer and detaches the old one, so the old buffer's byteLength becomes 0.
Fixed transferbuffer.transferToFixedLength()Moves bytes into a non-resizable destination.
Worker transfer listworker.postMessage(buffer, [buffer])Transfers ownership across threads without copying; the sender's buffer becomes detached.
Resizable buffer shapeJavaScript
const buffer = new ArrayBuffer(4, { maxByteLength: 8 });const fixed = new Uint8Array(buffer, 0, 4);const tracking = new Uint8Array(buffer);buffer.resize(6);console.log(fixed.length);console.log(tracking.length);buffer.resize(2);console.log(fixed.length);console.log(tracking.length);

Transfer is about ownership, not sharing. buffer.transfer() moves the bytes into a new buffer and detaches the old one. transferToFixedLength() does the same but guarantees the destination is not resizable. A detached buffer reports byteLength as 0 and its old views can no longer read useful bytes.

Transfer detaches the old bufferJavaScript
const buffer = new ArrayBuffer(1024);const moved = buffer.transfer();console.log(buffer.byteLength);console.log(moved.byteLength);const fixed = moved.transferToFixedLength();console.log(fixed.resizable);
postMessage transfer lists

worker.postMessage(buffer, [buffer]) transfers an ArrayBuffer to a worker without copying the bytes. That is different from structured cloning, and it is one reason the Messaging & cloning and Web Workers lessons matter for binary data.

Common misconceptions

Typed array traps to avoid
MisconceptionRealitySafer habit
ArrayBuffer is an arrayIt is raw memory with byteLength; views provide indexing and element types.Name variables buffer, bytes, view, or words to separate storage from interpretation.
All typed arrays choose the same byte orderMulti-byte typed arrays use native order; DataView lets you choose.Use DataView for file and protocol parsers.
subarray copiessubarray shares memory; slice copies.Use slice before mutating if callers need isolation.
Out-of-range writes throwInteger arrays wrap, clamped arrays clamp, floats round or overflow.Test conversions with real output before parsing untrusted bytes.
Transferred means sharedTransferred means the sender is detached; shared memory is SharedArrayBuffer plus Atomics.Pick transfer for ownership handoff, shared buffers for synchronized concurrency.

Practice exercises

Exercise 1 · Warm-upPredict conversion rules

Predict both output lines before running the code.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const signed = new Int8Array([127, 128, 129, -129]);
const clamped = new Uint8ClampedArray([-1, 12.5, 13.5, 300]);
console.log(Array.from(signed).join(","));
console.log(Array.from(clamped).join(","));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite a PNG size reader

    Write the two getUint32 reads a PNG size reader needs, then compare with the finished version.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const png = new Uint8Array([
      0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
      0x00, 0x00, 0x00, 0x0d,
      0x49, 0x48, 0x44, 0x52,
      0x00, 0x00, 0x03, 0x20,
      0x00, 0x00, 0x02, 0x58,
    ]);
    const view = new DataView(png.buffer, png.byteOffset, png.byteLength);
    const signatureOk = png.slice(0, 8).join(" ") === "137 80 78 71 13 10 26 10";
    const width = view.getUint32(16, false);
    const height = view.getUint32(20, false);
    console.log(signatureOk);
    console.log(width + "x" + height);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the shared-view bug

      A teammate expected both views to be independent. Find the bug by predicting the original bytes and the copied bytes after the shared view changes.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const bytes = new Uint8Array([1, 2, 3, 4]);
      const shared = bytes.subarray(1, 3);
      const copy = bytes.slice(1, 3);
      shared[0] = 99;
      console.log(bytes.join(","));
      console.log(copy.join(","));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeApply endian order

        Predict the two hexadecimal strings from the same four bytes.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const buffer = new ArrayBuffer(4);
        const view = new DataView(buffer);
        view.setUint32(0, 0x12345678, false);
        console.log(view.getUint32(0, false).toString(16));
        console.log(view.getUint32(0, true).toString(16));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · PracticeUse a 64-bit unsigned value

          Confirm that a 64-bit typed array stores a BigInt value exactly.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const ids = new BigUint64Array([2n ** 63n]);
          console.log(ids[0] === 9223372036854775808n);

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What is an ArrayBuffer by itself?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does the signed 8-bit write print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(new Int8Array([127, 128, 129, -129]).join(','));

              Choose an answer to see the explanation.

            3. Question 3 of 8What does the clamped pixel array print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(new Uint8ClampedArray([-1, 12.5, 13.5, 300]).join(','));

              Choose an answer to see the explanation.

            4. Question 4 of 8Which value belongs in a BigInt64Array constructor?

              Choose an answer to see the explanation.

            5. Question 5 of 8What does reading the same bytes as little-endian print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const b = new ArrayBuffer(4);
              const v = new DataView(b);
              v.setUint32(0, 0x12345678, false);
              console.log(v.getUint32(0, true).toString(16));

              Choose an answer to see the explanation.

            6. Question 6 of 8Which operation shares memory with the original typed array?

              Choose an answer to see the explanation.

            7. Question 7 of 8What happens to the sender's buffer after a successful transfer?

              Choose an answer to see the explanation.

            8. Question 8 of 8What is the current practical status of Float16Array?

              Choose an answer to see the explanation.

            Key takeaways

            • A byte is eight bits: an unsigned byte reads as 0–255, often written with two hex digits.
            • ArrayBuffer stores raw memory; typed arrays and DataView interpret it.
            • Integer typed arrays wrap, Uint8ClampedArray clamps and rounds for pixels, floats round to their precision, and BigInt arrays require BigInt values.
            • Use DataView for explicit offsets and endian order in binary formats.
            • subarray shares, slice copies, set bulk-copies, and ArrayBuffer.isView detects typed-array/DataView views.
            • Resizable buffers, transfer, worker transfer lists, and SharedArrayBuffer are about ownership and memory lifetime as much as bytes.

            Up next: Text encoding & Base64, where these same bytes become strings and encoded text.

            CompleteFrontend Clear concepts. Working examples.