← writing

27 Sept 2026 · 4 min read

Typed arrays that grow: what typed-numarray does, and what it actually saves

JavaScript's typed arrays are fast and fixed-size. typed-numarray makes them growable, gives them constant-time shift and unshift, and counting-sorts bounded integers. I re-ran the benchmarks, including the one it loses.

Typed arrays are the fastest numbers JavaScript has: one type, contiguous memory, no surprises. They also can't grow. The moment you need a push, you're back to a normal array, which can hold anything and has to be ready for anything.

typed-numarray is my attempt at both: a typed array you can push, pop, shift and unshift, with the usual array methods on top. Ten numeric types, from int8 up to 64-bit BigInt, one call:

import NumArray from "typed-numarray";

const scores = NumArray("int32", 10);
scores.push(42);
scores.unshift(7);
scores.shift(); // 7, in constant time

I published it in 2023. Three years later I re-read the code and re-ran the benchmarks, so this is the version with receipts.

A buffer with room at both ends

Every NumArray is an ArrayBuffer, a typed view over it, and two numbers: where the elements start, and how much capacity there is. Appending past the end doubles the capacity. Inserting at the front when there's no room grows the buffer by a quarter and puts all of that headroom in front, so a run of unshift calls doesn't move every element each time.

unshift
start moves back

shift
start moves forward,
nothing is copied

push
write after the end

free slots

elements

free slots

shift never copies anything: it moves the start index. That's the whole trick, and it is the part that pays for itself most.

Sorting integers by counting them

For integer arrays, sort() with no comparator looks at the value range first. If counting beats comparing, it counts:

var range = maxElement - minElement + 1;

if (this.length + range > this.length * Math.log2(this.length)) {
  return arr.sort((a, b) => a - b);
}

Past that check it tallies every value into a frequency array (a Uint8Array, Uint16Array or Uint32Array, whichever is just big enough to count the array's length) and writes the values back in order. Linear time, as long as the values are bounded.

What I measured

Node 26, median of three runs, 10 million random int32 values for the sorts and 100,000 operations for the deques:

task typed-numarray Array + comparator Int32Array#sort()
sort, values 0–1,000 116 ms 1,939 ms 349 ms
sort, values 0–10M 633 ms 2,777 ms 620 ms
sort, full int32 range 2,755 ms 3,264 ms 625 ms
100k × shift 2 ms 569 ms —
100k × unshift 3 ms 576 ms —
Sorting 10M int32s, millisecondsvalues 0..1kvalues 0..10Mfull int32 range300028002600240022002000180016001400120010008006004002000milliseconds

Orange: typed-numarray. Grey: a plain Int32Array#sort().

The README promises "over 5x" on sorting. Against a normal array it's 17× on bounded values, 4.4× on the 0–10M spread and 1.2× on the full range, so "5x" was an average of good days. Against a plain Int32Array, which V8 sorts natively when you pass no comparator, the story has three acts:

The deque numbers are the real story. A normal array's shift can move every remaining element; this one moves an index.

The benchmark that worked by accident

Every performance script in the repo creates its array like this:

var int32 = NumArray(int32, n);

No quotes. int32 isn't a string there; it's the variable being declared on that very line, which var hoisting has already created as undefined. Passing undefined triggers the default parameter, which happens to be "int32". So the benchmarks ran the right type for three years, entirely on vibes. The numbers above come from a fixed script.

When to use it

Reach for it when you need a numeric buffer that grows, especially one you consume from the front: a queue of events, a sliding window, BFS over millions of nodes. Skip it when you have a fixed-size array and just want it sorted: new Int32Array(data).sort() is already as fast as it gets, and on full-range values it currently beats mine. I'd rather you know that from me.

npm install typed-numarray

The source is on GitHub. It's also why my competitive-programming library stores its Fenwick tree in an Int32Array: once you've seen what typed memory does to a hot loop, it's hard to go back.

Written by Jay Pokale — researcher at IIT Hyderabad, top 1% competitive programmer, author of Chisle. Replies faster than his CI.