Heads up: posts on this site are drafted by Claude and fact-checked by Codex. Both can still get things wrong — read with care and verify anything load-bearing before relying on it.
why → how

Why WebAssembly exists

JavaScript already runs everywhere. So why did browser vendors agree to ship a second, lower-level execution target — and why is it now showing up in CDNs, plugin systems, and AI runtimes?

Computer Science intro Apr 29, 2026 · updated Aug 25, 2026 · 12 min read

On this page

The picture version

Five pictures for a reader who has run Figma in a browser tab without wondering how. The prose below fills in the seams the pictures skip.

1 · The problem

A C++ routine wants to run in your tab. Neither obvious route works.

a C++ image routine a photo editor wants to run ship the compiled machine code it’s x86 — useless on an ARM phone and it sees all of memory, so one bug owns the machine compile it to JavaScript portable and sandboxed — but megabytes of text to parse, and integer maths emulated on top of doubles One is the deal plugins offered, and the industry paid for it for two decades.
The second route is asm.js, and it genuinely worked — well enough that browser vendors looked at it and asked the obvious question. If we are already pretending JavaScript is a compile target, why not ship a real one?

2 · Design choice one

Hand the runtime a program whose types are already pinned down.

JavaScript “figure the types out as you go” guessdeoptre-guess the JIT re-derives what the C++ compiler already knew WebAssembly every value’s type is already known i32.add  f64.mul  local.get the validator checks the whole module in one linear pass Our routine’s int32 pixel arithmetic arrives as i32.add. not as a double the JIT has to prove is really an integer — which is what makes compiling it fast and predictable
This is the part JavaScript, by design, cannot do. You were describing a machine to a language built to hide machines.

3 · Design choice two

A room with no doors, and one hatch the host controls.

THE MODULE no filesystem no network no clock nothing, by default the host passes in a buffer of pixels, and a way back it cannot ask for a file, a socket, or the time — there is nothing in the room to ask with Capabilities arrive by import, or not at all. that is the property that turned a browser feature into something edge runtimes and plugin systems wanted
Note what this replaces: “load this .so and pray” becomes “load this module, and by design the worst it can do is misuse the API I gave it.” By design is doing real work in that sentence — runtime bugs still exist.

4 · What that bought, off the browser

Everyone who wanted lighter-than-a-container isolation.

edge compute ship customer code without a container per customer Fastly runs it end to end plugin systems Envoy and Istio expose a WASM extension API the module is the deployment unit portable binaries the same file across CPU architectures and operating systems though the imports it uses still matter “Why does WASM exist” has a 2015 answer and a 2026 answer. browsers needed a real compile target; the industry needed a portable sandbox. both are true, and the second is the one most engineers meet.
The isolation story isn’t uniform — Cloudflare Workers, for instance, uses V8 isolates as its primary boundary with WASM as a supported guest inside that. What the adopters share is the motive, not the architecture.

5 · Keep this card

The whole thing on one index card.

WebAssembly = portable bytecode + a capability-based sandbox + near-native speed ∴ the browser was the first host. it is no longer the only one.
Picture to keep: a sealed room with no doors and no windows, and a single hatch through which the host passes in exactly the tools it chooses. Our image routine gets handed a buffer of pixels and a way to hand pixels back. It cannot ask for a file, a socket, or the time, because there is nothing in the room to ask with.

Why it exists

You’ve opened Figma, or Google Earth, or an emulator playing a Nintendo game, in a browser tab. No install, no plugin, no “this site wants to run Java.” Twenty years ago each of those needed a downloaded application or a plugin that regularly turned into a security incident. Something changed in between, and it wasn’t that JavaScript got fast enough.

We’ll keep one concrete thing in view for the whole post: a C++ image-processing routine that a photo editor wants to run in your tab. Follow it, and the design falls out.

Before WebAssembly, that routine got there by a faintly perverse route. Look at any heavy in-browser app from the mid-2010s — photo editor, 3D game, CAD tool, emulator — and you find people writing the performance-critical code in C or C++, then compiling it through a chain of tools into a giant blob of JavaScript that the browser would re-parse, re-optimize, and run. The blob worked, but everything about it was strained. Parse times were huge. Number types were wrong (JavaScript only had doubles, so a C int32 had to be emulated). The JIT spent ages re-deriving facts the C++ compiler already knew and had thrown away. The format was a workaround.

The workaround had a name — asm.js — and it worked well enough that browser vendors looked at it and asked the obvious question: if we are already pretending JavaScript is a compile target, why not ship a real one?

That real one is WebAssembly. It exists because the web needed a way to run code that wasn’t written in JavaScript without paying the JavaScript tax — the parse cost, the type ambiguity, the dynamic dispatch — and without re-opening the security holes that NPAPI plugins like Flash and Java applets had spent two decades demonstrating.

Why it matters now

WASM started as a browser story but the part engineers actually trip over today is what happened off the browser. The WebAssembly spec defined a portable binary format with one interesting property: the runtime is a sandbox that gives the guest code zero ambient capabilities by default. No filesystem. No network. No clock. Anything the module can do, the host has to explicitly hand to it.

That property — capability-by-default-nothing — turns out to be exactly what a lot of non-browser systems were quietly looking for:

The question “why does WASM exist” has a 2015 answer (browsers needed a real compile target) and a 2026 answer (the industry needed a portable sandbox). Both are true; the second is what most engineers will actually run into.

The short answer

WebAssembly = portable bytecode + capability-based sandbox + near-native speed

Picture to keep: a sealed room with no doors and no windows, and a single hatch through which the host passes in exactly the tools it chooses. Our image routine gets handed a buffer of pixels and a way to hand pixels back. It cannot ask for a file, a socket, or the time, because there is nothing in the room to ask with.

WASM is a stack-machine bytecode designed to be (a) compiled to from C, C++, Rust, Go, and friends, (b) verified and JIT-compiled by the host quickly and predictably, and (c) executed inside a sandbox that gets exactly the imports the host chooses to grant it and nothing else. The browser was the first host. It is no longer the only one.

How it works

Try to get our C++ image routine into a browser tab yourself, and watch each attempt fail.

Attempt 1: just ship the compiled machine code. Fastest possible option, and a non-starter twice over. It’s x86 — useless on an ARM phone. And it’s native code with a flat view of memory, which means a bug in it (or a malicious version of it) owns the machine. This is roughly the deal NPAPI plugins offered, and the industry spent two decades paying for it.

Attempt 2: compile it to JavaScript. Portable, sandboxed, no new spec. This is asm.js, and it genuinely worked — but see the failures in the hook: megabytes of text to parse, the JIT re-deriving types the compiler already knew, and integer math emulated on top of doubles. You’re describing a machine to a language that was designed to hide machines.

The fix is a real compile target, and three design choices carry it.

1. The format is structured for fast, predictable compilation

A .wasm module is a binary file with explicit sections: types, imports, functions, memory, exports. Functions are encoded as typed stack-machine instructions — i32.add, f64.mul, local.get, call $foo. Types are static; every value at every point in the program has a known type the validator can check in one linear pass.

This is the part that JavaScript, by design, can’t do. JS’s type story is “figure it out as you go,” which forces the JIT to speculate, deoptimize, and re-specialize. WASM hands the runtime a program where the types are already pinned down — our image routine’s int32 pixel arithmetic arrives as i32.add, not as a double the JIT has to prove is really an integer. The result is that hosts can validate and compile a WASM module — JIT, AOT, or interpret, depending on the host — much more quickly and predictably than equivalent JS, which is what made it usable in browsers as a streaming compile target in the first place.

But solving speed reopens the security problem from attempt 1. Fast, statically typed code operating on raw memory is exactly what made native plugins dangerous. So:

2. Memory is a sandboxed linear buffer

The classic mental model — and still the common case in 2026 — is that a WASM module sees its memory as one contiguous, host-allocated ArrayBuffer (in the browser) or equivalent block elsewhere. (A multi-memory extension exists in modern engines, but the per-memory shape is the same.) All loads and stores are bounds-checked against that buffer. Our image routine can scribble anywhere in its own pixel buffer, including out past the end of its own arrays — that’s a bug, and it corrupts its own image. It cannot address anything outside the buffer: not other modules, not host memory, not the kernel.

This is what makes the sandbox cheap. There’s no need for a separate process or a hardware page table per guest; the bounds checks plus the lack of raw pointers to host objects do the work that an MMU usually does. (Modern engines elide most of those bounds checks at compile time using tricks like guard pages on 64-bit hosts, so the runtime cost in practice is small.)

But memory safety isn’t the whole attack surface. A module confined to its own buffer could still, if the runtime let it, open a socket or read a file. So:

3. The module can do nothing until the host imports give it powers

A freshly loaded WASM module has no syscalls, no fetch, no clock, no console.log. All of those are imports the host has to bind at instantiation time. If you don’t pass env.write_file, the module cannot write a file — there is no other way for it to try. This is the capability model, and it’s the property the non-browser hosts care about.

WASI is the attempt to standardize a set of these imports — a portable, capability-style “syscall” surface — so the same module can run in different non-browser runtimes. There are two release lines worth knowing: WASI Preview 1, the older shape that’s still widely used in production toolchains, and WASI 0.2 / Preview 2, which is built on top of the WebAssembly component model (a separate spec layer that adds typed interfaces between modules). Preview 1 and Preview 2 are not source-compatible. Which line dominates real deployments, and how fast Preview 2 is displacing Preview 1, isn’t something anyone publishes numbers on.

What WASM is not

The seam worth staring at

The single most underrated thing about WASM is what’s not in the spec. There is no I/O. No threading model in v1 (threads came later, behind a feature flag, and required browser-level coordination on shared memory). No string type. No exceptions originally. No 64-bit integers in the JS interop layer for years. Every one of those omissions was deliberate, and most of them have since been addressed as separately negotiated extensions — exceptions, SIMD, BigInt interop, GC. Some are still in flight: a first-class string type is a live proposal rather than a settled feature, and threads remain partly proposal-shaped even though browsers ship them.

That’s the engineering bet WASM made: ship a tiny, conservative core that can be implemented and verified by every browser quickly, and let the ecosystem argue over the extensions. Compare that to the way Java, Flash, and ActiveX shipped — huge runtime, huge attack surface, “trust us.” The minimalism is the security story. It’s also why writing real software in WASM still leans heavily on toolchain-provided runtimes (Emscripten, wasi-libc, wasm-bindgen) to paper over the gaps.

You started with WebAssembly = portable bytecode + capability-based sandbox + near-native speed. What did the image routine add? — + the sandbox is a consequence of what was left out, not a feature that was added. There’s no permission system to configure and no policy engine to get wrong; the module can’t reach the filesystem because no function that reaches the filesystem exists unless the host passes one in. That’s also the prediction to carry away: whenever someone proposes adding a capability to the core spec rather than to the imports, that’s the moment the security story starts eroding.

Going deeper