100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
WebAssembly

Memory Management in Wasm

How WebAssembly's linear memory model works, how it grows, and how it differs from garbage-collected host environments like JavaScript.

Running WasmAdvanced10 min readJul 10, 2026
Analogies

Linear Memory: One Big Byte Array

WebAssembly's memory model centers on linear memory: a single contiguous, byte-addressable buffer represented on the host side as a WebAssembly.Memory object backed by an ArrayBuffer. Everything a Wasm module allocates — a Rust Vec, a C struct, a string — lives at some numeric offset inside this one buffer, and Wasm functions read and write it using explicit load/store instructions (i32.load, i32.store, etc.) at byte offsets, with no implicit pointer arithmetic magic beyond what the compiled language runtime provides.

🏏

Cricket analogy: It's like a single long innings scorebook where every run, wicket, and extra is recorded at a specific numbered entry — there's no separate ledger per player, just one contiguous record everyone writes into at known positions.

Pages, Growth, and the 4GB Ceiling

Wasm memory is allocated in units of pages, each exactly 64 KiB (65,536 bytes). A WebAssembly.Memory({ initial: 10 }) starts with 10 pages (640 KiB); calling memory.grow(n) appends n more pages and returns the previous page count, or -1 if growth would exceed the configured maximum or the engine's own limits. In classic 32-bit Wasm (memory64 is a separate, newer proposal), pointers are 32-bit integers, capping a single linear memory at 4 GiB — a real constraint for data-heavy applications like video encoders or large in-memory databases compiled to Wasm.

🏏

Cricket analogy: It's like a stadium that only expands in fixed 5,000-seat stand blocks — you can't add just 200 seats, you add a whole new stand, and eventually you hit the ground's total land-boundary limit no matter how many stands you bolt on.

javascript
const memory = new WebAssembly.Memory({ initial: 2, maximum: 100 }); // pages
console.log(memory.buffer.byteLength); // 131072 (2 * 65536)

const previousPages = memory.grow(3); // add 3 more pages
console.log(previousPages);           // 2 (page count before growth)
console.log(memory.buffer.byteLength); // 327680 (5 * 65536)

// IMPORTANT: growing memory detaches the old ArrayBuffer reference
const view = new Uint8Array(memory.buffer); // must re-create views after grow()

Who Manages Allocation? It Depends on the Source Language

WebAssembly itself has no built-in allocator, no garbage collector, and no concept of malloc/free — memory management is entirely the responsibility of whatever runtime the source language brings with it. Rust compiled to wasm32-unknown-unknown embeds its own allocator (or wee_alloc for smaller binaries) and uses ownership/borrowing at compile time with zero runtime GC overhead. Emscripten-compiled C/C++ embeds dlmalloc. Languages with GC (like a Blazor/.NET app or Go, prior to the WasmGC proposal) had to ship an entire GC runtime inside the linear memory itself, which historically bloated binary size and added pause-time overhead absent from Rust or C.

🏏

Cricket analogy: It's like the difference between a franchise that manages its own player contracts entirely in-house (Rust's compile-time ownership, no runtime overhead) versus one that outsources roster management to an external agency that periodically audits and reshuffles the squad (a GC runtime doing periodic sweeps).

The WasmGC proposal (shipped in major engines around 2023-2024) adds native garbage-collected reference types to the Wasm spec itself, letting languages like Java, Kotlin, and Dart compile to Wasm without shipping their own GC implementation inside linear memory — the host engine's GC handles it instead.

Calling memory.grow() can reallocate the underlying ArrayBuffer to a new location, which detaches any existing TypedArray views (Uint8Array, Int32Array, etc.) constructed over the old buffer. Any code holding onto a stale view after a grow() call will silently operate on a zero-length, detached buffer — always re-create views from memory.buffer after growth.

  • Wasm linear memory is one contiguous, byte-addressable buffer backed by a WebAssembly.Memory / ArrayBuffer.
  • Memory is allocated in 64 KiB pages; memory.grow(n) adds pages and returns the previous page count or -1 on failure.
  • Classic 32-bit Wasm caps a single linear memory at 4 GiB due to 32-bit pointers.
  • Wasm has no built-in allocator or GC — memory management is delegated entirely to the source language's runtime.
  • Rust/C typically use manual or compile-time-checked allocators (dlmalloc, wee_alloc) with no runtime GC pause.
  • GC languages historically had to bundle an entire garbage collector inside linear memory, adding size and pause overhead.
  • growing memory detaches old ArrayBuffer views — TypedArray views must be re-created after every memory.grow() call.

Practice what you learned

Was this page helpful?

Topics covered

#WebAssembly#WebAssemblyStudyNotes#WebDevelopment#MemoryManagementInWasm#Memory#Management#Wasm#Linear#StudyNotes#SkillVeris