Skip to content

Memory Basics

WebAssembly modules can declare a block of raw bytes called linear memory. Unlike the JavaScript heap, this memory is a flat, untyped array of bytes that both the Wasm module and JavaScript can read and write directly. It is the primary mechanism for passing complex data — strings, arrays, structs — between WebAssembly and the host.

The (memory N) declaration reserves N pages of memory, where each page is exactly 64 KiB (65 536 bytes). The minimum useful size is one page:

(memory 1)

This gives your module 65 536 bytes of addressable memory, all initialised to zero at instantiation time.

WebAssembly provides typed load and store instructions for reading and writing values at specific byte addresses.

  • i32.store — pops an address and a 32-bit integer from the stack, writes 4 bytes at that address
  • i32.load — pops an address from the stack, reads 4 bytes and pushes an i32 result

Both instructions accept an optional static offset attribute that is added to the runtime address, which is useful for accessing fields within a struct.

JavaScript cannot access Wasm memory unless the module exports it. Use the inline export syntax:

(memory (export "mem") 1)

On the JavaScript side you can also create a WebAssembly.Memory object and pass it in as an import:

const memory = new WebAssembly.Memory({ initial: 1 });

Here is a complete module that stores an i32 value at byte address 100 and immediately loads it back:

(module
(memory (export "mem") 1)
(func (export "storeAndLoad") (param $val i32) (result i32)
i32.const 100 ;; address
local.get $val ;; value to store
i32.store
i32.const 100 ;; address
i32.load)) ;; load back

The stack discipline here is: push address, push value, call i32.store (consumes both). Then push address again, call i32.load (consumes address, pushes result).

The runner below stores 999 at offset 100, reads it back, and also reads mem[0] (which should be 0 since nothing was written there).

WebAssembly
OptionBenefitCost
A typed-array view directly over mem.bufferZero-copy byte read/write, no extra allocationMust be recreated every time memory grows, or the old view points at a detached buffer
Managing the memory layout yourself (computing addresses by hand)Full control over memory, no runtime overheadNo garbage collector or bounds-checking like JavaScript — you own the lifetime of the data
  • Writing past the boundary of the reserved pages (e.g. reserving (memory 1) but storing at an offset past 65,536), causing a trap because WebAssembly has no garbage collector or JavaScript-style bounds-checking layer
  • Forgetting that (memory N) can only grow a whole page (64 KiB) at a time — you can’t reserve just a few bytes, so it’s easy to over-allocate
  • Forgetting to export memory with (memory (export "mem") 1) and then wondering why JavaScript can’t reach instance.exports.mem

💡 Real-world example

Browser-based image editors in the style of Squoosh keep raw pixel buffers directly in linear memory instead of copying image data back and forth between JavaScript and Wasm on every operation, making resizing or compressing large images much faster.

How many bytes are in one WebAssembly memory page?
Which instruction writes a 4-byte integer to linear memory?
What is the initial value of all bytes in a freshly created WebAssembly memory?