Skip to content
Back to Blog

Total memory control

Total memory control

Look at these two lines long enough and a whole memory model falls out of the difference:

ts
const count = ref(0);      // Vue: the ref exists NOW

get count() {              // ivue: the ref exists WHEN ASKED
  return ref(0);
}

They look like a style choice. They are different physics.

Name and storage, fused

In the first line, the name is the storage. The moment setup() or a composable runs, the cell exists — it must, because the binding and the ref are the same thing. And from that fusion, three consequences follow that no discipline can undo:

  • You cannot defer it. Fifty refs in a class of state means fifty allocations at construction, touched or not.
  • You cannot release it. The closure holds it, the template holds it, and nothing can enumerate a closure's captures — the same privacy that protects the state makes its memory unreachable.
  • You cannot rebuild it. There is no "again"; the line already ran.

Vue gives you full control over two things: values (what a ref contains) and subscriptions (effectScope — what reacts to it). The third axis — existence, whether the cell is there at all — is simply not on the console. It was decided the moment the code ran.

One indirection

Put the ref behind a getter and the name detaches from the storage. The name is a prototype getter — permanent, shared by every instance, zero bytes each. The cell is an own-property behind it, created on first read, cached by the engine under a key it registered. session.user is always a valid sentence; whether a ref currently backs it is a separate, managed fact.

That one move puts the third axis on the console. Existence now has states, and an owner controls the transitions:

the cell is…how it got there
absentconstructed but never touched — costs nothing
presentfirst read materialized it
silenced but intact$stopEffects({ reset: false }) — watchers dead, value held
released$stopEffects() — deleted, collectable
rebuiltthe next touch, per cell — only what's read returns

The last row carries the finest grain: after a reset, touching three members of a fifty-member class re-materializes exactly three cells. Memory doesn't come back as a block. It comes back as observations.

The advanced form: keyed flyweight reactivity

Per-member existence is the entry level. The same indirection goes one grain finer when the getter's cell is not a single ref but a keyed storekeyed version signals: signals indexed by ids or coordinates that aren't known until runtime. Now existence is controlled per key: reading cell (1_048_202, F) materializes a signal for exactly that coordinate; writing to an unobserved key costs nothing at all; and the overlay evicts as observation moves away.

Take it all the way down and you get the flyweight pattern: ground truth in columnar typed arrays, disposable facades per render, and a sparse reactive overlay that exists only where the viewport looks — 20,000,000 live formula-capable cells in 89 MB, memory that never grows past what's on screen. One logical grid of twenty million reactive values; a few hundred actual signals at any moment. That is existence control at its natural limit: not "which members have cells" but "which observations do."

Here is the part I keep returning to: none of this was designed as a memory feature. The getter indirection was the original lazy-creation decision — and its consequences have been arriving in installments ever since, each one looking like a separate capability:

Four capabilities, one cause. That is what it means to find a real invariant instead of adding a feature: you pay for the indirection once, and it keeps handing you consequences years later — some of which you only recognize when you write them down.

The closure kept a secret

None of this is a defeat of Vue — every cell is Vue's own ref(), every subscription Vue's own effect. The control comes entirely from placement: own-properties are enumerable and deletable; closure captures are not. The closure's great virtue — privacy — turns out to have a price nobody itemized: it fuses a value's name, its storage, and its lifetime into one indivisible thing. Fine at component scale, where unmount collects everything. Decisive at domain scale, where models outlive views and twenty million of anything must answer for its bytes.

The name is grammar; the cell is memory. Keep them separate, and memory costs what observation costs — nothing more, ever.

One getter. That was the whole trick, three years ago. It is still paying.

Last updated:

Evgeny Kalashnikov
AuthorEvgeny KalashnikovLead Software Engineer@Blackline, Adhoc Studio
Share

Comments

More from the blog

Released under the MIT License.