JAVASCRIPT / MAPS, SETS, SYMBOLS, AND PROXIES
Typed arrays for fixed-size numeric lists
Store numbers in fixed-length typed arrays over an ArrayBuffer, predict how writes are coerced or dropped, and share bytes between overlapping views.
What you will learn
- Create fixed-length numeric lists with new Uint8Array(n) or new Uint8Array([...])
- Predict whether a write wraps, clamps, truncates, or is dropped entirely
- Overlay several views on one ArrayBuffer to reinterpret the same bytes
- Tell subarray (aliasing window) apart from slice (independent copy)
Understanding Typed arrays for fixed-size numeric lists
A typed array is not a JavaScript array. new Int32Array(4) reserves 16 contiguous bytes and gives you a window onto them in which every slot is read as a signed 32-bit integer. The length is fixed at construction and every slot starts at 0 rather than empty, because there is nothing in a block of bytes that could represent a hole. That is the trade: you give up the flexibility of Array and get a compact, predictable layout that binary APIs such as fetch, FileReader, WebGL, and Web Audio can hand you directly.
Because each slot is a fixed number of bits, assignment coerces instead of rejecting. A write to a Uint8Array runs the value through ToUint8, which truncates the fraction and then takes it modulo 256, so 300 becomes 44 and -1 becomes 255; Int16Array uses ToInt16, and Float32Array rounds to the nearest 32-bit float. Indexes are treated just as quietly: a write to an index outside the view is discarded and never becomes an ordinary property, and reading it gives undefined. That single rule is why a typed array can neither grow nor develop holes.
Owning the bytes and interpreting the bytes are separate jobs. An ArrayBuffer owns the memory; a typed array is a view described by a buffer, a byte offset, a length, and an element type. Several views can cover the same buffer at once, so a write through a Uint8Array is immediately visible through an Int32Array over those bytes, with no copying involved. subarray returns another view into the original buffer while slice allocates a new buffer and copies, and mixing the two up is the difference between mutating the source and leaving it alone.
const level = new Uint8Array(4);
console.log(level.length, level[0], level.byteLength);
level[0] = 200;
level[1] = 300; // ToUint8: 300 % 256
level[2] = -1; // wraps to the top of the range
level[3] = 12.9; // fraction truncated toward zero
console.log(level.join(','));
level[9] = 7; // out of bounds: dropped, no property created
console.log(level[9], level.length, Object.keys(level).join('|'));
const clamped = new Uint8ClampedArray([300, -1, 12.9]);
console.log(clamped.join(','));A typed array is a fixed-length, typed view onto raw bytes, so every write is coerced to fit the element type and any out-of-range index is silently discarded.
Worked examples
Two views, one buffer
Shows that separate typed arrays over the same ArrayBuffer read and write the identical bytes.
const buf = new ArrayBuffer(8);
const words = new Int32Array(buf);
const bytes = new Uint8Array(buf);
words[0] = 1;
console.log(bytes.join(','));
bytes[4] = 255;
bytes[5] = 255;
bytes[6] = 255;
bytes[7] = 255;
console.log(words[1]);
console.log(words.length, bytes.length, words.buffer === bytes.buffer);Example explained
Line 1Both constructors receive the same buffer, so words.buffer === bytes.buffer and no data is duplicated.
Line 2words[0] = 1 fills four bytes; on a little-endian machine the low byte comes first, which is why bytes starts 1,0,0,0.
Line 3Setting bytes 4 through 7 to 255 turns all 32 bits of the second word on, and Int32Array reads that pattern as -1 in two's complement.
Line 48 bytes divided by 4 bytes per element gives words.length of 2, while the byte view sees all 8.
subarray aliases, slice copies
Demonstrates that only one of the two range operations writes through to the original array.
const src = new Int16Array([10, 20, 30, 40]);
const view = src.subarray(1, 3);
const copy = src.slice(1, 3);
view[0] = 99;
copy[1] = 77;
console.log(src.join(','));
console.log(view.join(','), copy.join(','));
console.log(view.buffer === src.buffer, copy.buffer === src.buffer, view.byteOffset);Example explained
Line 1view[0] = 99 lands on src[1] because subarray only records a new offset and length over the existing buffer.
Line 2copy[1] = 77 leaves src untouched: slice allocated a fresh buffer and copied the two elements into it.
Line 3view.byteOffset is 2, not 1, because Int16Array elements are two bytes wide and offsets are measured in bytes.
The element type decides what survives
Compares how a 32-bit float, a 64-bit float, and a 16-bit integer store the same assigned values.
const f32 = new Float32Array(1);
const f64 = new Float64Array(1);
f32[0] = 0.1;
f64[0] = 0.1;
console.log(f32[0] === 0.1, f64[0] === 0.1);
console.log(f32[0]);
const ints = new Int16Array(1);
ints[0] = 40000;
console.log(ints[0]);Example explained
Line 1f32[0] = 0.1 rounds the double to the nearest 32-bit float, so reading it back yields a different number and the comparison fails.
Line 2Float64Array uses the same format as a plain JavaScript number, so 0.1 makes the round trip unchanged.
Line 340000 exceeds the Int16Array maximum of 32767, and ToInt16 wraps it into the negative half of the range as -25536.
Line 4None of these writes report a problem, so the type choice is the only place the loss can be caught.
Important notes
Multi-byte element types use the platform byte order, which is little-endian on essentially every machine you will meet, so bytes written through an Int32Array are not portable; use DataView with an explicit endianness flag for file formats and network protocols.
BigInt64Array and BigUint64Array store BigInt values only, and assigning a plain number to one throws a TypeError instead of converting it.
Common mistakes
Writing new Uint8Array(8) when you meant new Uint8Array([8]): the first is eight zeros, the second a single element holding 8, and the bug shows up as a wrong length far from the constructor.
Assuming arr[arr.length] = x appends: the write is silently discarded, so values vanish with no exception and no property to inspect afterwards.
Calling push to accumulate results, which throws TypeError: arr.push is not a function; size the view up front or gather into a plain array and convert at the end.
Using Uint8Array for values that can exceed 255 or go negative, where 256 becomes 0 and -1 becomes 255 without any warning.
Try it yourself
Change, predict, then run
Create a Uint8Array of length 5 and use a loop to write the values 253 through 257 into it, then log the elements joined by commas and explain which ones changed. Swap the constructor for Uint8ClampedArray, run it again, and describe why the last two elements differ.
Open the JavaScript workspaceCheck your understanding
For a Uint8Array of length 3, the code runs arr[3] = 5 followed by arr[0] = 300. What is the resulting state?
- arr[3] = 5 is discarded with no error, and arr[0] becomes 44 because 300 wraps modulo 256
- Both writes throw a RangeError, because typed arrays validate index and value ranges
- arr[3] = 5 extends the array to length 4, and arr[0] becomes 255 after clamping to the maximum byte
- arr[3] = 5 is discarded, and arr[0] = 300 throws a TypeError because 300 does not fit in one byte
Show answer
Typed arrays handle numeric index writes specially: an index outside the view is dropped and never becomes an ordinary property, so nothing is added and nothing is thrown. In-bounds values are converted with ToUint8, which is modular, so 300 lands on 44. Clamping to 255 is the behaviour of Uint8ClampedArray only, which is what makes option three tempting and wrong on both counts.