Pavan Siddapuram/ SE
← back to writing
DEC 2025· 2 MIN READ

Zero-copy transfers and what they taught me about respect for memory

JavaScript is the language that taught a generation of developers not to think about memory. The garbage collector handles it; the engine handles it; you, the developer, are free to allocate with abandon. This is mostly a gift. Occasionally it is a curse.

Where the abstraction breaks

The abstraction breaks the moment your buffer is large enough that copying it costs real time. A 200MB ArrayBuffer sent to a Web Worker via the default postMessage path is serialized, copied, and deserialized. On modern hardware, this is on the order of fifty milliseconds. For interactive software at 60fps, you have sixteen.

The escape hatch is the Transferable protocol. You pass the buffer in the second argument of postMessage:

worker.postMessage({ buffer }, [buffer]);

The buffer is now owned by the worker. The main thread's reference is detached — its byteLength is zero, and any read returns nothing. The transfer itself is, effectively, free.

The lesson

To pass cheaply, you must give up the right to use again.

This is the first time I have written JavaScript and felt the language pushing me toward Rust's notion of ownership. Only one place can own the buffer. The system enforces it. If you want to share, you copy; if you want to move, you transfer.

Why it matters beyond performance

The performance benefit is the obvious story. The deeper benefit is architectural. Once you start thinking in terms of ownership, the data flow of your application becomes legible in a way it wasn't before. You stop asking "who has a reference to this?" and start asking "who owns this?" — and the answer is always exactly one party.

I now design my workers as if they were small services. They take ownership of their inputs, do their work, and transfer ownership of their outputs back. The main thread orchestrates; it does not share.

Memory, it turns out, deserves respect — even in a language that pretends otherwise.