Growing Memory & Data Segments
A WebAssembly module starts with however many pages you declare in (memory N). But sometimes you need more memory at runtime — for example, when the user uploads a large file or your data grows beyond what you anticipated. The memory.grow instruction and the (data ...) segment cover these two related concerns: growing memory and pre-seeding it with static bytes.
The memory.grow instruction
Section titled “The memory.grow instruction”memory.grow takes one i32 argument from the stack: the number of additional pages to allocate. It returns the previous size in pages, or -1 if the grow failed (e.g., out of memory or would exceed the declared maximum).
(module (memory (export "mem") 1) (func (export "grow") (param $pages i32) (result i32) local.get $pages memory.grow))You can also grow memory from the JavaScript side using mem.grow(n):
const mem = instance.exports.mem;const oldPages = mem.grow(1); // returns previous page count, or -1Buffer detachment after grow
Section titled “Buffer detachment after grow”This is the most important gotcha: when WebAssembly memory grows, the underlying ArrayBuffer is detached. Any Uint8Array, Int32Array, or other typed view that was constructed from the old buffer becomes empty and unusable. You must re-create all views from mem.buffer immediately after every grow.
let view = new Uint8Array(mem.buffer); // valid before growmem.grow(1);// view is now detached — do NOT use itview = new Uint8Array(mem.buffer); // re-create from fresh bufferData segments
Section titled “Data segments”The (data ...) segment lets you embed static bytes directly in the module and have them written into memory automatically at instantiation time. This is how compiled languages initialise string literals and read-only tables.
(module (memory (export "mem") 1) (data (i32.const 0) "Hello")) ;; writes 5 UTF-8 bytes at offset 0The bytes are copied from the module into memory before any function runs, so the data is available immediately when your first exported function is called.
Run it
Section titled “Run it”The example below uses a (data ...) segment to write "Hi" at offset 0, grows memory by one page, re-creates the typed-array view after the grow, and checks that both the new size and the original data are correct.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
A typed-array view built from mem.buffer | Fast, zero-copy byte access, no copy overhead | Must be recreated every time after memory.grow, since the old buffer is detached |
| Managing memory growth and layout yourself | You control exactly when and how much to grow | No GC to help — you have to track which pointers/views are still valid after a grow |
Common mistakes
Section titled “Common mistakes”- Caching a typed-array view before calling
memory.growand forgetting to recreate it, so the view silently points at a detached buffer and reads/writes go wrong with no warning - Assuming
memory.grow(n)takes a byte count, when it actually takes a page count (each page is 64 KiB) — leading to accidentally requesting far more memory than intended - Placing a
(data ...)segment at an offset that overlaps memory JavaScript will later write to, silently overwriting the initial data
💡 Real-world example
FFmpeg.wasm uses a growable linear-memory buffer (
memory.grow) to stream decoded video frames directly between JavaScript and Wasm, without copying entire frames back and forth every time more memory is needed.