17. Memory is one long array
Every object, every array and every arrow you have drawn so far lives in one place: a single, enormous row of numbered bytes. Learn to see that row, and the rest of this book’s speed-ups start to make sense.
- Describe memory as one long array of bytes, and say what an address is.
- Work out where element i of a Float64Array sits in memory.
- Explain what a pointer is, and how it connects to chapter 4’s arrows.
- Contrast the call stack with the heap, and an array of objects with a contiguous typed array.
Where does the object go?
Part I gave you two pictures that never quite met. In chapter 4, a
variable held an arrow to an object. In chapter 16, a Float64Array
was a block of raw bytes. So where does the arrow actually point? And
what is it made of?
Here’s a small puzzle to keep in mind:
const xs = new Float64Array(1000000);
xs[999999] = 1; // instant
const ps = [];
for (let i = 0; i < 1000000; i++) {
ps.push({ x: i });
}
ps[999999].x = 1; // also instant
Both lines are fast. But they find their target in different ways, and by the end of Part II that difference will be worth a several-fold speed-up. To see it, you need one picture of the machine.
Picture a street so long it never seems to end, lined with tiny mailboxes. Every mailbox holds exactly one byte, and every mailbox has a number painted on it: 0, 1, 2, 3, and on into the billions. That street is your computer’s memory. To fetch something, you don’t need its name. You need its mailbox number.
One long row of bytes
Step through it. The row is far too long to draw in one line, so it wraps like text: each line of boxes continues where the last one stopped.
You can’t see real addresses from JavaScript, but a typed array shows you
the arithmetic. Each element of a Float64Array takes 8 bytes, and the
elements sit back to back:
const xs = new Float64Array(3);
console.log(xs.BYTES_PER_ELEMENT); // 8
console.log(xs.byteLength); // 24
Element i starts i × 8 bytes after the first one. That is why
xs[999999] is instant: the machine computes the spot, then goes there.
Memory (RAM) is one enormous array of bytes. A byte’s index in that
array is its address. Values that are stored side by side, with no
gaps between them, are contiguous. A typed array’s elements are
contiguous[3], so element i of a
Float64Array that starts at address b is at b + i × 8.
Arrows are addresses
Now the arrows from chapter 4 make sense. An object occupies some bytes starting at some address. A variable that “points at” the object simply stores that address: a number. Copying the variable copies the number, so two variables hold the same address, and that’s aliasing.
A pointer is a stored address. A JavaScript reference is a pointer
the engine manages for you: you can follow it (p.x) and copy it
(q = p), but you can never see or change the number inside.
How big is a pointer? On a 64-bit machine an address is 64 bits, so a full pointer takes 8 bytes. V8, the engine in Chrome and Node, can squeeze them into 4 bytes by storing a 32-bit offset from a base address instead, a trick called pointer compression[2]. Either way, an arrow costs real bytes.
Two neighbourhoods: the stack and the heap
Memory is one row, but programs divide it into regions with different jobs. Two matter to us. You met the first in chapter 9: the call stack, where each active call keeps a frame for its local variables. The second is the heap: a large region where objects are put as they are created[1].
Look at the two collections in that picture. They hold the same kind of data, but in very different shapes:
ps, an array of objects, is really a row of pointers. Each one leads somewhere else. To read everyx, the machine reads a pointer, jumps to an object, readsx, comes back, and repeats.xs, aFloat64Array, is the numbers, packed side by side. To read them all, the machine walks straight along one block.
The call stack holds one frame per active call, and a frame’s memory is released when its call returns. The heap holds objects. They stay as long as something can still reach them, however many calls come and go. Together these are the stack and heap: the two regions where a JavaScript program’s data lives.
Build your own memory
You can model all of this with an ordinary array of numbers. Let mem
be memory and its indexes be addresses. A list node takes two slots: a
value, then the address of the next node. Address 0 means “no next
node”, the way null does:
const mem = [0, 0, 10, 5, 0, 20, 0];
// address 2: value 10, next node at 5
// address 5: value 20, next 0 (the end)
Start at address 2, read the value, then follow the pointer stored next to it. Run it, then change the pointers to reorder the list without moving a single value.
// mem is memory; indexes are addresses.
// A node: [value, address of next node]
const mem = [0, 0, 10, 5, 0, 20, 0];
let p = 2; // address of the first node
while (p !== 0) {
console.log('at', p, 'value', mem[p]);
p = mem[p + 1]; // follow the pointer
}This toy is closer to the truth than it looks. An engine sees the heap
as a big array of bytes, and an object’s fields as a few slots at known
offsets from its start. When it runs p.x, it reads the pointer in p,
adds the offset of x, and loads the bytes there. In V8, JavaScript
values live on the V8 heap and are referred to by pointers[2].
Viscosity’s job, in these terms, is to take a heap full of small objects
connected by pointers and rewrite it into a few long, contiguous typed
arrays. Chapter 18 shows why the machine cares so much.
Your turn
Memory is modelled as an array mem. Each list node takes two slots: mem[p] is its value and mem[p + 1] is the address of the next node, with 0 meaning “no next node”. Write sumList(mem, head) that starts at address head and returns the sum of every value in the list. If head is 0, the list is empty.
function sumList(mem, head) {
// your code here
}
const mem = [0, 0, 10, 5, 0, 20, 0];
console.log(sumList(mem, 2)); // 30Particles are packed into one Float64Array that starts at address base. Each particle has 6 fields (x, y, vx, vy, mass, health, numbered 0 to 5), stored one particle after another, 8 bytes per field. Write fieldAddr(base, i, f) that returns the address of field f of particle i.
function fieldAddr(base, i, f) {
// your code here
}
// mass (field 4) of particle 2:
console.log(fieldAddr(0, 2, 4)); // 128What you learned
- Memory is one long array of bytes. A byte’s index is its address.
- Contiguous values sit side by side. In a
Float64Arraystarting atb, elementiis atb + i × 8. - A pointer is a stored address. JavaScript references are pointers that the engine manages for you. A full one is 8 bytes on a 64-bit machine.
- The stack and heap: call frames on the stack, objects on the heap.
- An array of objects is a row of pointers to objects that may be anywhere. A typed array is one contiguous block of numbers.
Check your understanding
0/6- 1.
A
Float64Arraystarts at address 200. Where does element 5 start? - 2.
In memory, what is a JavaScript reference to an object?
- 3.
const ps = [{ x: 1 }, { x: 2 }];What do the two slots ofpshold? - 4.
Why does an object created inside a function survive after the function returns?
- 5.
Which word describes how a Float64Array’s elements are stored?
- 6.
To read
ps[i].xfor every particle in an array of objects, what must the machine do each time?
References
- [1] MDN contributors. JavaScript execution model. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model (checked 2026-10-31)
- [2] Igor Sheludko, Santiago Aboy Solanes (2020). Pointer Compression in V8. V8 blog (v8.dev), 30 March 2020. v8.dev/blog/pointer-compression (checked 2026-10-31)
- [3] MDN contributors. JavaScript typed arrays. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays (checked 2026-10-31)