WebAssembly (WASM) started as a way to run C++ in the browser. In 2026, it runs everywhere - browsers, servers, edge networks, embedded devices - and powers some of the most demanding applications on the web. Figma's rendering engine, Adobe Photoshop on the web, Google Meet's video processing, and Cloudflare's edge compute platform all run on WebAssembly.
Chrome Platform Status puts WASM at roughly 5.5% of all Chrome page loads as of early 2026, up from 4.5% the year before. With WASM 3.0 becoming a W3C standard and WASI maturing toward 1.0, the ecosystem has reached a turning point.
This guide covers everything you need to know to start building with WebAssembly.
What is WebAssembly?
WebAssembly is a binary instruction format designed as a compilation target. You write code in a high-level language (Rust, C, C++, Go, Kotlin), compile it to .wasm, and run it in any environment that has a WASM runtime - browsers, Node.js, Cloudflare Workers, Wasmtime, or Wasmer.
How It Works
WASM is a stack-based virtual machine. Functions push and pop values on an operand stack. The host runtime (V8 in Chrome, SpiderMonkey in Firefox) JIT-compiles the WASM bytecode to native machine code, which is why performance is near-native.
Key characteristics:
- Sandboxed execution: WASM modules can only access resources the host explicitly grants. No filesystem, no network, no OS access unless permitted. This is fundamentally different from native code.
- Linear memory: a single contiguous
ArrayBuffershared between WASM and the host. Complex data (strings, structs) is passed by writing to memory and sharing a pointer. - Type-limited: WASM natively supports only four types:
i32,i64,f32,f64. Everything else (strings, arrays, objects) requires encoding through linear memory or the Component Model. - Portable: the same
.wasmbinary runs on any platform with a WASM runtime, without recompilation.
WASM vs JavaScript
WASM does not replace JavaScript. It complements it.
| Aspect | JavaScript | WebAssembly |
|---|---|---|
| Parsing | Parse + compile at runtime | Pre-compiled binary, decode only |
| Execution speed | JIT-optimized, variable | Near-native, consistent |
| Startup | Fast for small scripts | Fast decode, predictable |
| DOM access | Direct | Indirect (through JS glue) |
| Best for | UI, DOM manipulation, event handling | CPU-intensive computation |
| Garbage collection | Built-in | WasmGC (new), or manual |
Use JavaScript for UI and DOM work. Use WASM for heavy computation: image processing, video encoding, physics simulations, cryptography, data parsing.
WASM 3.0: What's New
WebAssembly 3.0 became a W3C standard in September 2025, standardizing nine features that had been in development for years.
| Feature | What It Enables |
|---|---|
| WasmGC | Native garbage collection in WASM. Managed languages (Java, Kotlin, Dart) can compile to WASM without shipping their own GC runtime. Supported in Chrome 119+, Firefox 120+, Safari 18.2+. |
| Exception Handling | Native try/catch in WASM. Previously, exceptions required expensive roundtrips to JavaScript. |
| Tail Calls | Enables efficient recursion without stack overflow. Critical for functional languages. |
| Relaxed SIMD | 128-bit vector instructions for parallel data processing. Enables hardware-specific optimizations. |
| Memory64 | Breaks the 4GB linear memory limit. Required for large-scale data processing. |
| Multi-memory | Multiple independent memory regions in one module. |
The most impactful is WasmGC. Before it, compiling Java or Kotlin to WASM meant shipping an entire garbage collector as part of the binary, bloating file sizes. Now the browser's own GC handles memory management for WASM modules, just like it does for JavaScript.
WASI: WebAssembly Beyond the Browser
WASM in the browser is powerful, but WASI (WebAssembly System Interface) is what makes WASM a universal runtime. WASI provides standardized interfaces for system resources - files, networking, clocks, random numbers - allowing WASM modules to run outside the browser.
WASI Preview 2 (the current stable release) defines these interfaces:
wasi:filesystem- file operations via capability handles (not traditional file descriptors)wasi:sockets- TCP/UDP networkingwasi:http- HTTP request/response handlingwasi:clocks- wall clock, monotonic clockwasi:random- cryptographic randomnesswasi:cli- command-line arguments, environment variables, stdio
The key principle is capability-based security: a WASM module cannot access the filesystem unless the host explicitly grants a handle to a specific directory. This makes WASI fundamentally more secure than running native executables.
The Path to WASI 1.0
WASI 0.3.0 (adding async/concurrency primitives) is expected in 2026, with WASI 1.0 to follow. The main addition is language-integrated async with zero-copy streaming I/O.
The Component Model
Core WASM modules can only exchange numbers. The Component Model solves this limitation by adding a rich type system and composability layer on top of WASM.
WIT (WebAssembly Interface Types)
WIT is an Interface Definition Language that lets components declare their imports and exports with rich types - strings, records, lists, variants, enums - not just i32 and f64.
// calculator.wit package myorg:calculator@1.0.0; interface operations { record calculation { expression: string, result: f64, timestamp: u64, } add: func(a: f64, b: f64) -> f64; multiply: func(a: f64, b: f64) -> f64; history: func() -> list<calculation>; } world calculator { export operations; }
Toolchains like wit-bindgen generate language-specific bindings from WIT files. A Rust component and a Python component can exchange strings, records, and lists through WIT contracts without either side knowing the other's implementation language.
Building Your First WASM Module with Rust
Rust has the most mature WASM tooling. Let's build a practical example: an image processing module that runs in the browser.
Setup
# Install the WASM target for Rust rustup target add wasm32-unknown-unknown # Install wasm-pack (builds Rust to WASM + generates JS bindings) cargo install wasm-pack # Create a new library project cargo new --lib image-processor cd image-processor
Configure Cargo.toml
[package] name = "image-processor" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2"
Write the Rust Code
// src/lib.rs use wasm_bindgen::prelude::*; /// Convert an image buffer to grayscale. /// Input: RGBA pixel data as a flat u8 array (4 bytes per pixel). /// Output: Modified in place for zero-copy performance. #[wasm_bindgen] pub fn grayscale(pixels: &mut [u8]) { for chunk in pixels.chunks_exact_mut(4) { let r = chunk[0] as f32; let g = chunk[1] as f32; let b = chunk[2] as f32; // ITU-R BT.709 luminance coefficients let gray = (0.2126 * r + 0.7152 * g + 0.0722 * b) as u8; chunk[0] = gray; chunk[1] = gray; chunk[2] = gray; // chunk[3] is alpha, leave unchanged } } /// Adjust brightness of an image. /// factor > 1.0 brightens, < 1.0 darkens. #[wasm_bindgen] pub fn adjust_brightness(pixels: &mut [u8], factor: f32) { for chunk in pixels.chunks_exact_mut(4) { chunk[0] = ((chunk[0] as f32 * factor).min(255.0)) as u8; chunk[1] = ((chunk[1] as f32 * factor).min(255.0)) as u8; chunk[2] = ((chunk[2] as f32 * factor).min(255.0)) as u8; } } /// Invert all colors in the image. #[wasm_bindgen] pub fn invert(pixels: &mut [u8]) { for chunk in pixels.chunks_exact_mut(4) { chunk[0] = 255 - chunk[0]; chunk[1] = 255 - chunk[1]; chunk[2] = 255 - chunk[2]; } } /// Calculate the average brightness of an image (0-255). #[wasm_bindgen] pub fn average_brightness(pixels: &[u8]) -> f32 { let mut total: f64 = 0.0; let pixel_count = pixels.len() / 4; for chunk in pixels.chunks_exact(4) { let luminance = 0.2126 * chunk[0] as f64 + 0.7152 * chunk[1] as f64 + 0.0722 * chunk[2] as f64; total += luminance; } (total / pixel_count as f64) as f32 }
Build
wasm-pack build --target web
This produces a pkg/ directory with:
image_processor_bg.wasm- the compiled WASM binaryimage_processor.js- JavaScript glue code with TypeScript definitionspackage.json- ready to publish to npm
Use in JavaScript
<!DOCTYPE html> <html> <head><title>WASM Image Processor</title></head> <body> <canvas id="canvas" width="800" height="600"></canvas> <button onclick="applyGrayscale()">Grayscale</button> <button onclick="applyBrightness()">Brighten</button> <button onclick="applyInvert()">Invert</button> <script type="module"> import init, { grayscale, adjust_brightness, invert } from "./pkg/image_processor.js"; let ctx; let imageData; async function setup() { await init(); const canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d"); // Load an image onto the canvas const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0); imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); }; img.src = "photo.jpg"; } window.applyGrayscale = () => { grayscale(imageData.data); ctx.putImageData(imageData, 0, 0); }; window.applyBrightness = () => { adjust_brightness(imageData.data, 1.3); ctx.putImageData(imageData, 0, 0); }; window.applyInvert = () => { invert(imageData.data); ctx.putImageData(imageData, 0, 0); }; setup(); </script> </body> </html>
The key insight: imageData.data is a Uint8ClampedArray backed by an ArrayBuffer. When passed to WASM, it shares the same memory - no copying. The Rust function modifies pixels in place, and the JavaScript side sees the changes immediately.
Lower-Level: Manual WASM Instantiation
If you don't want to use wasm-bindgen, you can instantiate WASM modules directly:
const response = await fetch("calculator.wasm"); const { instance } = await WebAssembly.instantiateStreaming(response, { env: { // Functions the WASM module can call log_result: (value) => console.log("Result:", value), }, }); // Call exported functions const { add, multiply } = instance.exports; console.log(add(5, 3)); // 8 console.log(multiply(4, 7)); // 28
This is useful when you want minimal overhead and don't need rich type interop.
Performance: WASM vs JavaScript
Real-world benchmarks show significant speedups for CPU-intensive tasks:
| Task | JavaScript | WASM | Speedup |
|---|---|---|---|
| 4K image processing | 180ms | 8ms (with SIMD) | 22x |
| Image resize (4K) | 250ms | 45ms | 5.5x |
| Physics simulation (10K entities) | Drops frames | Smooth 60fps | ~10x |
| JSON parsing (large payload) | 12ms | 3ms | 4x |
| Cryptographic hashing | 45ms | 6ms | 7.5x |
WASM runs at roughly 95% of native code speed. The biggest gains come from:
- Predictable performance (no JIT warmup, no GC pauses)
- SIMD instructions for parallel data processing
- Direct memory access without garbage collector interference
Where WASM is NOT faster: DOM manipulation, small computations, I/O-bound tasks. JavaScript is already optimized for these.
Production Use Cases
Figma: Real-Time Vector Rendering
Figma's core rendering engine is C++ compiled to WASM. Every shape, gradient, and effect is computed in WASM and drawn to a Canvas element. This allows Figma to handle complex designs with thousands of layers at 60fps in the browser - performance that would be impossible in pure JavaScript.
Adobe Photoshop on the Web
Adobe ported key Photoshop filters and tools to WASM using Rust. Their benchmarks show 4K image processing in 22ms with WASM SIMD vs 180ms in JavaScript - an 8x improvement that makes real-time filter previews possible.
Cloudflare Workers
Cloudflare runs WASM modules in V8 isolates across 330+ edge locations. Cold starts are 1-5ms (compared to 100-500ms for container-based serverless). In February 2026, they deployed Llama-3-8b inference across their edge network using WASM.
Google Meet
Background blur and virtual backgrounds in Google Meet use WASM with SIMD for real-time video processing. The WASM module processes each video frame fast enough to maintain smooth video at 30fps.
Browser Support (2026)
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Core WASM | Full | Full | Full | Full |
| Threads | Yes | Yes | Yes | Yes |
| SIMD (128-bit) | Yes | Yes | Yes | Yes |
| WasmGC | 119+ | 120+ | 18.2+ | Yes |
| Exception Handling | Yes | Yes | Yes | Yes |
| Memory64 | Yes | Yes | Partial | Yes |
All major browsers fully support WASM. The newer features (WasmGC, Exception Handling) have reached broad availability.
Tooling Reference
| Tool | Purpose | Install |
|---|---|---|
| wasm-pack | Build Rust to WASM, generate npm packages | cargo install wasm-pack |
| wasm-bindgen | Rust/JS interop bindings (used by wasm-pack) | Dependency in Cargo.toml |
| wasm-opt | Binary size optimization (50%+ reduction) | Part of Binaryen: brew install binaryen |
| wit-bindgen | Generate bindings from WIT files | cargo install wit-bindgen-cli |
| Wasmtime | Server-side WASM runtime (reference WASI implementation) | brew install wasmtime |
| Wasmer | Alternative WASM runtime with WASI support | curl https://get.wasmer.io -sSfL | sh |
| wasm-feature-detect | Runtime browser feature detection | npm install wasm-feature-detect |
Optimizing Binary Size
WASM binaries can be large. Here's how to shrink them:
# Cargo.toml [profile.release] opt-level = "z" # Optimize for size lto = true # Link-time optimization codegen-units = 1 # Better optimization, slower compile strip = true # Strip debug symbols
# Build in release mode wasm-pack build --release --target web # Further optimize with wasm-opt wasm-opt -Oz pkg/image_processor_bg.wasm -o pkg/image_processor_bg.wasm
A typical Rust WASM module goes from 500KB to under 50KB with these optimizations.
Getting Started Roadmap
Conclusion
WebAssembly is no longer experimental. It is a production technology used by some of the most demanding applications on the web. Near-native performance, sandboxed security, and universal portability - no other compilation target gives you all three.
You don't need to rewrite your entire application in WASM. Start with a single CPU-intensive function - an image filter, a data parser, a physics calculation - compile it to WASM, and call it from JavaScript. Measure the difference. Then decide where else WASM can help.
The tooling is mature, the browser support is universal, and the ecosystem is growing. If you write Rust, you are already one command away from the browser.
Getting Started Checklist:
- Rust and wasm-pack installed
- First WASM module built and running in the browser
- JavaScript interop working (calling WASM from JS)
- Release build with size optimizations applied
- Performance benchmarked against pure JavaScript equivalent
- WASI explored with Wasmtime for server-side use cases