Typed arrays
A typed array is a fixed-length window onto raw bytes where every element has the same numeric type. Twelve classes exist and they differ in exactly one respect: what an element is.
In Deno this is not an advanced topic. Deno.readFile gives you a Uint8Array, TextEncoder produces one, crypto.getRandomValues fills one, a fetch body streams them, and Deno.stdout.write takes one. If you do any input or output at all, Uint8Array is the currency, and it is the only one of the twelve most people ever name. Textbooks motivate the feature with WebGL and WebAssembly; ignore that, because the reason to learn this is that bytes arrive whether you asked for them or not.
Create programs/typed-arrays.test.ts for this reference and keep it open. Start it with the assertion helpers used on this page:
import {
assert,
assertEquals,
assertFalse,
assertStrictEquals,
assertThrows,
} from "@std/assert";
Follow the page as you add and revise the runnable examples below that import.
four ways to make one, and they agree
Deno.test("four ways to make one, and they agree", () => {
const fromArray = new Uint8Array([0, 1, 2]);
const fromArguments = Uint8Array.of(0, 1, 2);
const converted = Uint8Array.from([0, 1, 2]);
const byHand = new Uint8Array(3);
byHand[0] = 0;
byHand[1] = 1;
byHand[2] = 2;
assertEquals(fromArray, fromArguments);
assertEquals(converted, byHand);
assertEquals([...byHand], [0, 1, 2]);
});
Check programs/typed-arrays.test.ts
running 1 test from ./programs/typed-arrays.test.ts
four ways to make one, and they agree ... ok (346µs)
ok | 1 passed | 0 failed (2ms)
Reading and writing by index works as it does on an array. The fourth form is the one worth noticing: new Uint8Array(3) gives you three zeros, where new Array(3) gives three holes, from the arrays page. A typed array is never empty in the middle.
a length in elements, a size in bytes
Deno.test("a length in elements, a size in bytes", () => {
const pairs = new Int16Array(2);
assertStrictEquals(pairs.length, 2);
assertStrictEquals(pairs.byteLength, 4);
assertStrictEquals(Int16Array.BYTES_PER_ELEMENT, 2);
assertStrictEquals(pairs.buffer.byteLength, 4);
assertEquals([...new Uint8Array(4)], [0, 0, 0, 0]);
assertThrows(
() => new Uint8Array(-1),
RangeError,
"Invalid typed array length: -1",
);
});
a length in elements, a size in bytes ... ok (354µs)
Two lengths, and mixing them up is the first mistake everyone makes: length counts elements, byteLength counts bytes, and the ratio between them is BYTES_PER_ELEMENT. buffer is where the bytes actually live, because a typed array does not contain its elements; it is a view onto an ArrayBuffer that does, which is the subject of the buffers and views page. The twelve classes, for reference: Int8Array, Uint8Array, and Uint8ClampedArray at one byte; Int16Array, Uint16Array, and Float16Array at two; Int32Array, Uint32Array, and Float32Array at four; BigInt64Array, BigUint64Array, and Float64Array at eight. The two BigInt classes work in bigint values rather than number, and Uint8ClampedArray exists for canvas pixel data, differing from Uint8Array in one respect covered below.
iterable, and convertible both ways
Deno.test("iterable, and convertible both ways", () => {
const bytes = Uint8Array.of(1, 2, 3);
assertEquals([...bytes], [1, 2, 3]);
assertEquals(Array.from(bytes), [1, 2, 3]);
assertEquals(bytes.values().toArray(), [1, 2, 3]);
assertEquals(Uint8Array.from([1, 2, 3]), bytes);
assertEquals(new Uint8Array([1, 2, 3]), bytes);
});
iterable, and convertible both ways ... ok (249µs)
Typed arrays are iterable, so for-of, spread, and destructuring all work, and the iterator carries the helper methods from the iterator helpers page. Out to an array with spread or Array.from, back in with the constructor or .from().
writing an element coerces it, and out of range wraps
A Uint8Array holds zero to 255. Write 256, and predict what you read back:
Deno.test("writing an element coerces it, and out of range wraps", () => {
function setAndGet(target: Uint8Array, value: number): number {
target[0] = value;
return target[0];
}
const unsigned = new Uint8Array(1);
assertStrictEquals(setAndGet(unsigned, 255), 255);
assertStrictEquals(setAndGet(unsigned, 256), 255);
});
Check programs/typed-arrays.test.ts
running 4 tests from ./programs/typed-arrays.test.ts
...
writing an element coerces it, and out of range wraps ... FAILED (14ms)
ERRORS
writing an element coerces it, and out of range wraps => ./programs/typed-arrays.test.ts:54:6
error: AssertionError: Values are not strictly equal.
[Diff] Actual / Expected
- 0
+ 255
FAILURES
writing an element coerces it, and out of range wraps => ./programs/typed-arrays.test.ts:54:6
FAILED | 3 passed | 1 failed (15ms)
error: Test failed
One past the top became the bottom. A typed array will store whatever you give it, converted to fit, silently: there is no error and no warning for a value out of range, and integers wrap by modulo arithmetic. Uint8ClampedArray is the exception and the only reason it exists, holding out-of-range values at the nearest end instead, which is what you want for a pixel and wrong for almost everything else. This is the sharpest difference from an array, and worth internalising as a rule: assigning into a typed array is a conversion, not a store. TypeScript helps a little, since writing a string is a type error, but it has no way to check a range. Correct the prediction and pin all three classes:
Deno.test("writing an element coerces it, and out of range wraps", () => {
function setAndGet(
target: Uint8Array | Int8Array | Uint8ClampedArray,
value: number,
): number {
target[0] = value;
return target[0];
}
const unsigned = new Uint8Array(1);
assertStrictEquals(setAndGet(unsigned, 255), 255);
assertStrictEquals(setAndGet(unsigned, 256), 0);
assertStrictEquals(setAndGet(unsigned, -1), 255);
const signed = new Int8Array(1);
assertStrictEquals(setAndGet(signed, 127), 127);
assertStrictEquals(setAndGet(signed, 128), -128);
assertStrictEquals(setAndGet(signed, -129), 127);
const clamped = new Uint8ClampedArray(1);
assertStrictEquals(setAndGet(clamped, 256), 255);
assertStrictEquals(setAndGet(clamped, -1), 0);
});
writing an element coerces it, and out of range wraps ... ok (165µs)
a float overflows to infinity and underflows to zero
Deno.test("a float overflows to infinity and underflows to zero", () => {
const half = new Float16Array(1);
const store = (value: number) => {
half[0] = value;
return half[0];
};
assertStrictEquals(store(2 ** 15), 32768);
assertStrictEquals(store(2 ** 16), Infinity);
assertStrictEquals(store(-(2 ** 16)), -Infinity);
assertStrictEquals(store(2 ** -24), 5.960464477539063e-8);
assertStrictEquals(store(2 ** -25), 0);
});
a float overflows to infinity and underflows to zero ... ok (31µs)
Floats do not wrap. Too large in either direction becomes the matching infinity, and too close to zero becomes zero, gone without complaint, which is ordinary floating-point behaviour at a smaller size than the numbers page works in. Float16Array is ES2025 and real in Deno, along with Math.f16round for rounding to that precision without allocating anything.
there are no holes, and an index past the end is dropped
Deno.test("there are no holes, and an index past the end is dropped", () => {
assertEquals(Object.keys(new Uint8Array(3)), ["0", "1", "2"]);
const bytes = Uint8Array.of(1);
bytes[9] = 5;
assertStrictEquals(bytes.length, 1);
assertStrictEquals(bytes[9], undefined);
assertEquals(Object.keys(bytes), ["0"]);
const negative = Uint8Array.of(6, 7);
negative[-1] = 5;
assertEquals(Object.keys(negative), ["0", "1"]);
assertStrictEquals(negative[-1], undefined);
assertStrictEquals(negative.at(-1), 7);
const labelled = Object.assign(Uint8Array.of(1, 2), { label: "sample" });
assertEquals(Object.keys(labelled), ["0", "1", "label"]);
});
there are no holes, and an index past the end is dropped ... ok (74µs)
Every index from zero to length - 1 exists, always, and the length never changes, so none of the hole behaviour the arrays page has to catalogue applies here, which is a genuine simplification. Compare the two negative-index stories side by side, because they are the clearest illustration of how different these structures are underneath: on an array, arr[-1] = 5 creates a property, growing a member nobody wanted, where on a typed array the write is dropped entirely, silently. Both are quiet, and the typed array's silence is the one you would have chosen; the same goes for an index past the end. It is still an object, though, so a named property works as it would anywhere, as labelled shows. What a typed array refuses is an out-of-range index, not a property.
a smaller method set, and sort is different
Deno.test("a smaller method set, and sort is different", () => {
const bytes = Uint8Array.of(1);
for (
const missing of ["concat", "push", "pop", "shift", "unshift", "splice"]
) {
assertFalse(missing in bytes, missing);
}
assertFalse("flat" in bytes);
assertFalse("flatMap" in bytes);
assertFalse("toSpliced" in bytes);
for (const present of ["at", "with", "toSorted", "toReversed", "subarray"]) {
assert(present in bytes, present);
}
assertEquals([...Uint8Array.of(200, 3, 10).sort()], [3, 10, 200]);
assertEquals([200, 3, 10].sort(), [10, 200, 3]);
});
a smaller method set, and sort is different ... ok (73µs)
Everything that would change the length is absent: no push, pop, shift, unshift, splice, or concat, which follows from the length being fixed, and toSpliced is missing for the same reason, since its result would have a different length. flat and flatMap are absent too, because the elements are numbers and cannot nest. Everything else you know is there, including the copies with, toSorted, and toReversed from the mutating arrays page. And then the good surprise: sort is numeric by default. The central warning of the ordering and sorting page, that a bare sort compares string representations and scrambles numbers, does not apply here, as the side-by-side shows, because a typed array knows its elements are numbers. You may still pass a comparator, and you almost never need to.
map keeps the class, which is the precision trap
source is an Int8Array, doubled on its way into an Int16Array, where every doubled value fits. Predict the result:
Deno.test("map keeps the class, which is the precision trap", () => {
const source = Int8Array.of(127, 126, 125);
assertEquals([...Int16Array.from(source.map((n) => n * 2))], [254, 252, 250]);
});
Check programs/typed-arrays.test.ts
running 8 tests from ./programs/typed-arrays.test.ts
...
map keeps the class, which is the precision trap ... FAILED (12ms)
ERRORS
map keeps the class, which is the precision trap => ./programs/typed-arrays.test.ts:136:6
error: AssertionError: Values are not equal.
[Diff] Actual / Expected
[
- -2,
- -4,
- -6,
+ 254,
+ 252,
+ 250,
]
FAILURES
map keeps the class, which is the precision trap => ./programs/typed-arrays.test.ts:136:6
FAILED | 7 passed | 1 failed (16ms)
error: Test failed
map on a typed array returns the same class, so the results are coerced back into the original element type before you see them, and combined with silent wrapping that is the worst bug in this entry: source.map(n => n * 2) ran inside Int8Array, where 254 does not fit, and the wrapped values were then faithfully widened to 16 bits. TypeScript cannot help, because the wrong version and the right version have the same types throughout; nothing is out of place, and the arithmetic just happened in the wrong room. The rule: when converting between element types, do the conversion first, or pass the mapping function to .from() and let one operation do both. Correct the prediction and pin both right ways beside the wrong one:
Deno.test("map keeps the class, which is the precision trap", () => {
const mapped = Uint8Array.of(1, 2).map((n) => n * 100);
assertStrictEquals(mapped.constructor, Uint8Array);
const source = Int8Array.of(127, 126, 125);
assertEquals([...Int16Array.from(source, (n) => n * 2)], [254, 252, 250]);
assertEquals([...Int16Array.from(source).map((n) => n * 2)], [254, 252, 250]);
assertEquals([...Int16Array.from(source.map((n) => n * 2))], [-2, -4, -6]);
});
map keeps the class, which is the precision trap ... ok (228µs)
concatenation is set, since there is no concat
Deno.test("concatenation is set, since there is no concat", () => {
function concatenate(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {
const total = parts.reduce((sum, part) => sum + part.length, 0);
const result = new Uint8Array(total);
let offset = 0;
for (const part of parts) {
result.set(part, offset);
offset += part.length;
}
return result;
}
assertEquals(
[...concatenate(Uint8Array.of(1, 2), Uint8Array.of(3))],
[1, 2, 3],
);
const target = new Uint8Array(2);
target.set([9], 1);
assertEquals([...target], [0, 9]);
assertThrows(
() => target.set([1, 2, 3]),
RangeError,
"offset is out of bounds",
);
});
concatenation is set, since there is no concat ... ok (92µs)
set(source, offset) copies a typed array or an array-like into an existing one starting at offset, counted in elements. Since the result has a fixed length, you have to know the total before you allocate, which is why concatenate makes two passes, and why streaming APIs hand you chunks and expect you to accumulate rather than concatenate as you go. Worth keeping in a utility file. And set will not grow the target, saying so rather than truncating.
slice copies, subarray shares
shared is a two-element window onto base. Write through it, and predict base:
Deno.test("slice copies, subarray shares", () => {
const base = Uint8Array.of(1, 2, 3, 4);
const shared = base.subarray(1, 3);
shared[0] = 99;
assertEquals([...base], [1, 2, 3, 4]);
});
Check programs/typed-arrays.test.ts
running 10 tests from ./programs/typed-arrays.test.ts
...
slice copies, subarray shares ... FAILED (9ms)
ERRORS
slice copies, subarray shares => ./programs/typed-arrays.test.ts:179:6
error: AssertionError: Values are not equal.
[Diff] Actual / Expected
[
1,
- 99,
+ 2,
3,
4,
]
FAILURES
slice copies, subarray shares => ./programs/typed-arrays.test.ts:179:6
FAILED | 9 passed | 1 failed (12ms)
error: Test failed
The write went through to the original. subarray returns a new view onto the same bytes, with the same buffer and a byteOffset saying where it starts, where slice copies, like the array method of the same name. This is the likeliest real bug in the entry, because both results are typed arrays that print the same way and behave identically until someone writes: the sharing rule from the values and references page at the byte level, and the aliasing the buffers and views page builds its model on. Reach for subarray deliberately, because it is free, which matters when the data is large, and it is aliasing, which matters always. Correct the prediction and pin both methods side by side:
Deno.test("slice copies, subarray shares", () => {
const base = Uint8Array.of(1, 2, 3, 4);
const shared = base.subarray(1, 3);
const copied = base.slice(1, 3);
assertStrictEquals(shared.buffer, base.buffer);
assertFalse(copied.buffer === base.buffer);
assertStrictEquals(shared.byteOffset, 1);
shared[0] = 99;
copied[1] = 88;
assertEquals([...base], [1, 99, 3, 4]);
assertEquals([...copied], [2, 88]);
});
slice copies, subarray shares ... ok (30µs)
TypeScript parameterises the buffer
A modern surprise, and one you will meet before you understand it. Put the strict spelling and a shared buffer together in a scratch file programs/buffer-generic.ts:
function needsRealBuffer(bytes: Uint8Array<ArrayBuffer>): number {
return bytes.buffer.byteLength;
}
declare const shared: Uint8Array<ArrayBufferLike>;
needsRealBuffer(shared);
Check programs/buffer-generic.ts
TS2345 [ERROR]: Argument of type 'Uint8Array<ArrayBufferLike>' is not assignable to parameter of type 'Uint8Array<ArrayBuffer>'.
Type 'ArrayBufferLike' is not assignable to type 'ArrayBuffer'.
Type 'SharedArrayBuffer' is missing the following properties from type 'ArrayBuffer': resizable, resize, detached, transfer, transferToFixedLength
needsRealBuffer(shared);
~~~~~~
at file:///programs/buffer-generic.ts:7:17
error: Type checking failed.
Uint8Array is generic over the kind of buffer behind it, and the two spellings are not interchangeable. new Uint8Array(3), new TextEncoder().encode(text), and Deno.readFile all produce Uint8Array<ArrayBuffer>, the specific one. Bare Uint8Array means Uint8Array<ArrayBufferLike>, which also admits a SharedArrayBuffer, and the diagnostic names exactly what is missing from that class, the same five members the buffers and views page confirmed absent with in checks. The rule that follows is short: write bare Uint8Array in a parameter position, because it accepts both, and reach for Uint8Array<ArrayBuffer> only when the function genuinely needs to resize or transfer the buffer. Delete the scratch file and pin the rule the accepting way around:
Deno.test("TypeScript parameterises the buffer", () => {
function needsRealBuffer(bytes: Uint8Array<ArrayBuffer>): number {
return bytes.buffer.byteLength;
}
function acceptsAny(bytes: Uint8Array): number {
return bytes.length;
}
assertStrictEquals(needsRealBuffer(new Uint8Array(3)), 3);
assertStrictEquals(acceptsAny(new TextEncoder().encode("ok")), 2);
const shared = new Uint8Array(new SharedArrayBuffer(2));
assertStrictEquals(acceptsAny(shared), 2);
});
TypeScript parameterises the buffer ... ok (27µs)
text is bytes, and the encoding is UTF-8
Deno.test("text is bytes, and the encoding is UTF-8", () => {
const encoded = new TextEncoder().encode("oak");
assertEquals([...encoded], [111, 97, 107]);
assertStrictEquals(new TextDecoder().decode(encoded), "oak");
assertStrictEquals(new TextEncoder().encode("🙂").length, 4);
assertStrictEquals("🙂".length, 2);
});
text is bytes, and the encoding is UTF-8 ... ok (92µs)
TextEncoder and TextDecoder are platform APIs rather than parts of the language, and they are the conversion you will use constantly. TextEncoder always produces UTF-8 and takes no options; TextDecoder accepts an encoding name, for reading data that is not UTF-8. The last two lines are the same character measured three ways: four bytes as UTF-8, two units as a JavaScript string, and one thing a reader would call a character. The text and characters page is about that gap, and this is where it becomes a number you have to allocate for.
hex and base64 are methods now
Deno.test("hex and base64 are methods now", () => {
const bytes = Uint8Array.of(255, 1, 0);
assertStrictEquals(bytes.toHex(), "ff0100");
assertStrictEquals(bytes.toBase64(), "/wEA");
assertStrictEquals(
Uint8Array.of(255, 239).toBase64({ alphabet: "base64url" }),
"_-8=",
);
assertEquals(Uint8Array.fromHex("ff0100"), bytes);
assertEquals(Uint8Array.fromBase64("/wEA"), bytes);
assertThrows(
() => Uint8Array.fromHex("f"),
SyntaxError,
"Input string must contain hex characters in even length",
);
});
hex and base64 are methods now ... ok (61µs)
toHex, toBase64, Uint8Array.fromHex, and Uint8Array.fromBase64 are new, present in Deno, and fully typed, unlike the rawJSON pair the json page had to cast into reach. They replace two things you will find in older code: a hand-written loop over toString(16), and the btoa(String.fromCharCode(...bytes)) trick, which is fiddly and wrong for large inputs. fromHex also rejects a bad string rather than guessing.
the superclass has no global name
Deno.test("the superclass has no global name", () => {
assert(ArrayBuffer.isView(new Uint8Array(1)));
assert(ArrayBuffer.isView(new DataView(new ArrayBuffer(1))));
assertFalse(ArrayBuffer.isView([]));
assertStrictEquals(Object.getPrototypeOf(Uint8Array).name, "TypedArray");
});
the superclass has no global name ... ok (18µs)
The twelve classes share a superclass that the specification calls TypedArray and that has no global name, as the prototype walk shows, so you cannot write instanceof TypedArray. ArrayBuffer.isView is the substitute, and it says yes to a DataView too.
The whole entry
Run the whole reference suite:
Check programs/any-unknown-never.test.ts
Check programs/arrays.test.ts
Check programs/assignment.test.ts
Check programs/async-functions.test.ts
Check programs/async-iteration.test.ts
Check programs/branching.test.ts
Check programs/buffers-and-views.test.ts
Check programs/classes.test.ts
Check programs/closures.test.ts
Check programs/conversion-and-coercion.test.ts
Check programs/dates-and-times.test.ts
Check programs/designing-error-types.test.ts
Check programs/destructuring.test.ts
Check programs/equality.test.ts
Check programs/errors-and-exceptions.test.ts
Check programs/functions.test.ts
Check programs/generators.test.ts
Check programs/iterables-and-iterators.test.ts
Check programs/iterator-helpers.test.ts
Check programs/json.test.ts
Check programs/loops.test.ts
Check programs/maps.test.ts
Check programs/matching-and-replacing.test.ts
Check programs/module-specifiers.test.ts
Check programs/modules.test.ts
Check programs/mutating-arrays.test.ts
Check programs/nothing-twice.test.ts
Check programs/numbers.test.ts
Check programs/objects-as-dictionaries.test.ts
Check programs/objects.test.ts
Check programs/ordering-and-sorting.test.ts
Check programs/parameters-and-arguments.test.ts
Check programs/private-class-members.test.ts
Check programs/promise-combinators.test.ts
Check programs/promises.test.ts
Check programs/prototypes-and-inheritance.test.ts
Check programs/read-only.test.ts
Check programs/regular-expressions.test.ts
Check programs/scope-and-declarations.test.ts
Check programs/sentinels.test.ts
Check programs/sets.test.ts
Check programs/strings.test.ts
Check programs/subclassing.test.ts
Check programs/symbols.test.ts
Check programs/tagged-templates.test.ts
Check programs/text-and-characters.test.ts
Check programs/the-event-loop.test.ts
Check programs/the-value-of-this.test.ts
Check programs/transforming-arrays.test.ts
Check programs/truthiness.test.ts
Check programs/typed-arrays.test.ts
Check programs/unicode-in-patterns.test.ts
Check programs/unions-and-narrowing.test.ts
Check programs/values-and-references.test.ts
Check programs/weak-collections.test.ts
Check programs/what-a-type-is.test.ts
running 10 tests from ./programs/any-unknown-never.test.ts
...
running 13 tests from ./programs/arrays.test.ts
...
running 9 tests from ./programs/assignment.test.ts
...
running 10 tests from ./programs/async-functions.test.ts
...
running 11 tests from ./programs/async-iteration.test.ts
...
running 10 tests from ./programs/branching.test.ts
...
running 12 tests from ./programs/buffers-and-views.test.ts
...
running 11 tests from ./programs/classes.test.ts
...
running 6 tests from ./programs/closures.test.ts
...
running 11 tests from ./programs/conversion-and-coercion.test.ts
...
running 13 tests from ./programs/dates-and-times.test.ts
...
running 10 tests from ./programs/designing-error-types.test.ts
...
running 14 tests from ./programs/destructuring.test.ts
...
running 11 tests from ./programs/equality.test.ts
...
running 10 tests from ./programs/errors-and-exceptions.test.ts
...
running 11 tests from ./programs/functions.test.ts
...
running 12 tests from ./programs/generators.test.ts
...
running 14 tests from ./programs/iterables-and-iterators.test.ts
...
running 12 tests from ./programs/iterator-helpers.test.ts
...
running 11 tests from ./programs/json.test.ts
...
running 14 tests from ./programs/loops.test.ts
...
running 15 tests from ./programs/maps.test.ts
...
running 15 tests from ./programs/matching-and-replacing.test.ts
...
running 6 tests from ./programs/module-specifiers.test.ts
...
running 12 tests from ./programs/modules.test.ts
...
running 10 tests from ./programs/mutating-arrays.test.ts
...
running 11 tests from ./programs/nothing-twice.test.ts
...
running 15 tests from ./programs/numbers.test.ts
...
running 14 tests from ./programs/objects-as-dictionaries.test.ts
...
running 13 tests from ./programs/objects.test.ts
...
running 12 tests from ./programs/ordering-and-sorting.test.ts
...
running 11 tests from ./programs/parameters-and-arguments.test.ts
...
running 11 tests from ./programs/private-class-members.test.ts
...
running 11 tests from ./programs/promise-combinators.test.ts
...
running 11 tests from ./programs/promises.test.ts
...
running 12 tests from ./programs/prototypes-and-inheritance.test.ts
...
running 12 tests from ./programs/read-only.test.ts
...
running 13 tests from ./programs/regular-expressions.test.ts
...
running 9 tests from ./programs/scope-and-declarations.test.ts
...
running 8 tests from ./programs/sentinels.test.ts
...
running 13 tests from ./programs/sets.test.ts
...
running 10 tests from ./programs/strings.test.ts
...
running 11 tests from ./programs/subclassing.test.ts
...
running 10 tests from ./programs/symbols.test.ts
...
running 8 tests from ./programs/tagged-templates.test.ts
...
running 10 tests from ./programs/text-and-characters.test.ts
...
running 9 tests from ./programs/the-event-loop.test.ts
...
running 10 tests from ./programs/the-value-of-this.test.ts
...
running 13 tests from ./programs/transforming-arrays.test.ts
...
running 9 tests from ./programs/truthiness.test.ts
...
running 14 tests from ./programs/typed-arrays.test.ts
four ways to make one, and they agree ... ok (394µs)
a length in elements, a size in bytes ... ok (373µs)
iterable, and convertible both ways ... ok (64µs)
writing an element coerces it, and out of range wraps ... ok (58µs)
a float overflows to infinity and underflows to zero ... ok (37µs)
there are no holes, and an index past the end is dropped ... ok (68µs)
a smaller method set, and sort is different ... ok (74µs)
map keeps the class, which is the precision trap ... ok (52µs)
concatenation is set, since there is no concat ... ok (85µs)
slice copies, subarray shares ... ok (65µs)
TypeScript parameterises the buffer ... ok (44µs)
text is bytes, and the encoding is UTF-8 ... ok (107µs)
hex and base64 are methods now ... ok (73µs)
the superclass has no global name ... ok (25µs)
running 11 tests from ./programs/unicode-in-patterns.test.ts
...
running 13 tests from ./programs/unions-and-narrowing.test.ts
...
running 13 tests from ./programs/values-and-references.test.ts
...
running 9 tests from ./programs/weak-collections.test.ts
...
running 7 tests from ./programs/what-a-type-is.test.ts
...
ok | 626 passed | 0 failed (1s)
Fourteen tests, and the practice is short. Uint8Array unless you have a reason, because it is what every Deno API speaks, and reaching for Float32Array to hold ordinary numbers buys silent precision loss and a smaller method set in exchange for nothing. Bare Uint8Array in signatures, which accepts a file's contents, an encoder's output, and a slice of a shared buffer alike. subarray only when sharing is what you want, and slice otherwise. Convert element types with .from(), not with map. Hex and base64 are methods now, so retire the hand-rolled loops. And for the bytes underneath, byte offsets, endianness, and what happens when a buffer is resized or transferred, the buffers and views page is the other half of this one.