Viscosity
Review
Part II · How machines run code · Day 5

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.

About 15 minutes, including the exercises and quiz
By the end you will be able to
  • 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.

Intuition

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.

Step throughMemory as numbered bytes
Memory drawn as rows of 16 numbered byte boxes, with a Float64Array, an object and a pointer placed in it012345678910111213141501632486480
This is memory: one long row of bytes, each with a number called its address. It is far too long to draw in one line, so it wraps like text. The left column gives the address of each row’s first byte.
1/6

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.

Definition

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.

Definition

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].

The call stack holds a frame with variables n, ps and xs. ps points to an array in the heap whose three slots point to three objects in different places. xs points to one contiguous block of three numbers.CALL STACKHEAPframe: mainn3psxsps (array)xs (Float64Array)3 numbers × 8 bytes, side by side
Frames on the stack hold arrows into the heap. Inside main’s frame, n holds a number; ps and xs hold pointers. The array ps is a row of pointers, and each points at a particle object somewhere else in the heap. The Float64Array’s three numbers sit in one contiguous block. Positions are illustrative: you never choose where things go.

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 every x, the machine reads a pointer, jumps to an object, reads x, comes back, and repeats.
  • xs, a Float64Array, is the numbers, packed side by side. To read them all, the machine walks straight along one block.
Definition

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.

Try itA linked list in a toy memory
// 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
}
Under the hood

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

ExerciseFollow the pointers

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)); // 30
Extra practiceAddress of a field in a packed buffer

Particles 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)); // 128

What you learned

  • Memory is one long array of bytes. A byte’s index is its address.
  • Contiguous values sit side by side. In a Float64Array starting at b, element i is at b + 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. 1.

    A Float64Array starts at address 200. Where does element 5 start?

  2. 2.

    In memory, what is a JavaScript reference to an object?

  3. 3.

    const ps = [{ x: 1 }, { x: 2 }]; What do the two slots of ps hold?

  4. 4.

    Why does an object created inside a function survive after the function returns?

  5. 5.

    Which word describes how a Float64Array’s elements are stored?

  6. 6.

    To read ps[i].x for every particle in an array of objects, what must the machine do each time?

References

  1. [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. [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. [3] MDN contributors. JavaScript typed arrays. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays (checked 2026-10-31)