WebAssembly Cheat Sheet
Covers core WebAssembly concepts, compiling Rust to Wasm, calling Wasm functions from JavaScript, and when Wasm actually outperforms JS.
Core WebAssembly Concepts
The building blocks of the Wasm execution model.
- Wasm module- Portable binary instruction format (.wasm) compiled from Rust, C/C++, Go, etc.
- Linear memory- A single contiguous, resizable ArrayBuffer that Wasm code reads/writes as its heap
- Sandbox- Wasm executes in a memory-safe sandbox isolated from the host, same-origin policy still applies
- Imports/Exports- Modules declare functions/memory they need from JS and expose functions to JS
- WASI- WebAssembly System Interface — standardizes OS-like capabilities for non-browser Wasm runtimes
- Near-native speed- Ahead-of-time validated bytecode runs close to native speed for CPU-bound work
Compiling Rust to Wasm
A wasm-bindgen function, built with wasm-pack.
// lib.rsuse wasm_bindgen::prelude::*;#[wasm_bindgen]pub fn fibonacci(n: u32) -> u64 { match n { 0 => 0, 1 => 1, _ => { let (mut a, mut b) = (0u64, 1u64); for _ in 2..=n { let c = a + b; a = b; b = c; } b } }}// Build with wasm-pack (produces .wasm + JS glue code)// $ wasm-pack build --target web
Loading & Calling Wasm from JavaScript
Two ways to load a module: raw and via wasm-bindgen glue.
// Loading a hand-written/compiled .wasm module directlyconst { instance } = await WebAssembly.instantiateStreaming( fetch('module.wasm'), { env: { log: (val) => console.log('from wasm:', val) } } // imports);const result = instance.exports.add(2, 3); // call an exported function// Loading a wasm-bindgen (Rust) packageimport init, { fibonacci } from './pkg/my_module.js';await init(); // loads and instantiates the .wasm binaryconsole.log(fibonacci(20)); // 6765
When to Use WebAssembly
Where Wasm actually pays off versus plain JS.
- CPU-intensive computation- Image/video processing, physics simulations, cryptography, codecs
- Porting existing native code- Reuse mature C/C++/Rust libraries (e.g. ffmpeg, SQLite) in the browser
- Games and 3D engines- Unity and Unreal can export to WebAssembly for browser-based games
- Not a DOM replacement- Wasm has no direct DOM access; it still needs JS glue code to touch the page
- Sandboxed plugin execution- Run untrusted third-party code safely (e.g. serverless edge functions, plugins)
Growing Linear Memory & Passing Strings
Manually manage the Wasm heap and marshal UTF-8 strings across the JS/Wasm boundary without wasm-bindgen glue.
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 }); // pages of 64KiBconst { instance } = await WebAssembly.instantiateStreaming( fetch('module.wasm'), { env: { memory } });// Grow memory on demand (returns previous size in pages, or -1 on failure)const prevPages = memory.grow(1);// Strings aren't a Wasm type -- you pass a (ptr, len) pair into linear memoryfunction writeString(str, ptr) { const bytes = new TextEncoder().encode(str); new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes); return bytes.length;}function readString(ptr, len) { const view = new Uint8Array(memory.buffer, ptr, len); return new TextDecoder('utf-8').decode(view);}// The module must export an allocator so JS knows where it's safe to writeconst ptr = instance.exports.alloc(64);const len = writeString('hola mundo', ptr);instance.exports.greet(ptr, len);instance.exports.dealloc(ptr, 64);
WebAssembly.Table & Indirect Calls
Function pointers in Wasm: a table of callable references invoked via call_indirect, mirrored from JS.
// A Table holds function references (Wasm has no raw function pointers into linear memory)const table = new WebAssembly.Table({ initial: 4, element: 'anyfunc' });const { instance } = await WebAssembly.instantiateStreaming( fetch('dispatch.wasm'), { env: { callback_table: table } });// Install a JS function at a table slot so Wasm can invoke it by indexfunction jsCallback(x) { return x * 2; }const wrapped = new WebAssembly.Function( { parameters: ['i32'], results: ['i32'] }, jsCallback);table.set(0, wrapped);// Wasm side (WAT) resolves the call at runtime through the table:// (call_indirect (type $unary) (local.get $index))// Invoke a Wasm-side function stored in the table directly from JSconst fn = table.get(1);console.log(fn(21)); // whatever the Wasm function at slot 1 returns
Shared Memory & Atomics (Threads Proposal)
Run Wasm across Web Workers with a SharedArrayBuffer-backed memory and atomic operations for safe concurrent access.
// Memory must be `shared: true` and the page needs COOP/COEP headers// (Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp)const memory = new WebAssembly.Memory({ initial: 1, maximum: 10, shared: true, // backed by SharedArrayBuffer instead of ArrayBuffer});// Main thread: spin up workers, each instantiating the SAME module + memoryconst worker = new Worker('worker.js');worker.postMessage({ memory }); // structured-clone shares the SAB, not a copy// Inside worker.js, after instantiating with the shared memory import:const i32 = new Int32Array(memory.buffer);Atomics.add(i32, 0, 1); // atomic increment, race-free across threadsAtomics.store(i32, 1, 42);const result = Atomics.load(i32, 1);// Block a worker until another thread signals (never do this on the main thread)Atomics.wait(i32, 2, 0); // sleep while i32[2] === 0Atomics.notify(i32, 2, 1); // wake one waiter
Advanced Wasm Proposals & Features
Post-MVP capabilities that unlock more of the platform beyond the original spec.
- SIMD (v128)- 128-bit vector type and instructions for data-parallel numeric code (audio, image, ML kernels)
- Multi-value- Functions and blocks can return more than one value without a struct/pointer workaround
- Tail calls- return_call/return_call_indirect reuse the current stack frame, enabling deep recursion without overflow
- Exception handling- Native try/catch/throw instructions replace the old JS-trampoline error propagation hack
- Wasm GC- Managed, garbage-collected struct/array reference types, letting languages like Kotlin/Dart target Wasm directly
- Component Model- WIT-defined interfaces that let independently compiled Wasm components interop across languages without hand-written glue
- Reference types- externref/funcref as first-class values, enabling safer host-object handles without linear memory pointers
Running Wasm Outside the Browser (WASI + Wasmtime)
Compile to a WASI target and execute with filesystem/env capabilities on a standalone runtime.
# Compile a Rust binary to the WASI target instead of the browser targetrustup target add wasm32-wasip1cargo build --release --target wasm32-wasip1# Run with Wasmtime, granting only the capabilities you explicitly pass inwasmtime run \ --dir=. \ --env API_KEY=secret \ target/wasm32-wasip1/release/my_tool.wasm -- --input data.csv# WASI is capability-based: the sandboxed module can ONLY touch the# directories/env vars explicitly preopened above -- no ambient filesystem access
WebAssembly isn't automatically faster than JavaScript for everything — startup/compile overhead and the JS-to-Wasm call boundary can outweigh gains for small, simple functions. Reach for it for sustained, heavy CPU-bound work, not routine logic.