What Is WebAssembly? A Practical Introduction

WebAssembly, usually shortened to Wasm, is a portable binary instruction format. You compile a program written in a language like C, C++, Rust, or Go into a .wasm module, and that module runs at near-native speed inside a sandboxed virtual machine. The same binary runs on any host that implements the Wasm spec, whether that host is a browser tab, a server process, or an edge node on a CDN.

That is the short version. The longer version is worth understanding, because Wasm quietly became one of the more useful building blocks in modern infrastructure, and the reasons it exists say a lot about where it fits.

Why it exists #

For years the browser had exactly one language: JavaScript. If you wanted to run a codec, a physics engine, an image filter, or a cryptography library on the client, you rewrote it in JavaScript or you gave up. That was slow to build and slow to run. JavaScript is fast for what it is, but it is a dynamically typed, garbage-collected language, and there is a ceiling on how fast a JIT can make it for compute-heavy work.

Wasm was designed to raise that ceiling. It gives the browser a low-level compile target that maps closely to real CPU instructions, so a C or Rust program can run at speeds JavaScript cannot reach. The original pitch was simple: bring fast, non-JavaScript code to the web without plugins like Flash or Native Client. Every major browser shipped support, and the format has been stable since 2017.

What surprised people is what happened next. The same properties that made Wasm good for the browser (portability, a tight sandbox, fast startup) turned out to be valuable on servers and at the edge too.

How it works at a high level #

The flow is straightforward:

code

source code (Rust / C / Go)
|
| compile
v
module.wasm (portable binary)
|
| load + instantiate
v
Wasm runtime (browser VM, Wasmtime, V8, etc.)
|
| imports / exports
v
host functions (JS glue, WASI, plugin API)

You compile your code to a .wasm module. The host loads that module into a runtime and instantiates it. From there the module and the host talk through a narrow, explicit interface: the module exports functions the host can call, and it imports functions the host provides. Nothing outside that interface is reachable.

This is the core of the security model. A Wasm module has no ambient access to the filesystem, the network, or memory it was not given. It runs in linear memory the runtime controls, and it can only touch the outside world through imports the host chose to hand it. That capability-based design is why Wasm makes a good sandbox for untrusted code.

The browser story vs the server story #

In the browser, Wasm is a companion to JavaScript, not a replacement. You typically load a .wasm module from JavaScript, call into it for the heavy work, and get results back. The module cannot touch the DOM directly, so anything that manipulates the page still goes through a JavaScript glue layer. Figma runs its rendering and editing engine as Wasm. ffmpeg.wasm puts a full media toolchain in the browser with no server round trip.

On the server and edge, the picture is different because there is no DOM to worry about. Here the missing piece was system access: a Wasm module needs a defined way to read files, open sockets, and read the clock. That is what WASI, the WebAssembly System Interface, provides. WASI is a standard set of host functions that give a module controlled, capability-scoped access to the system.

Edge functions: platforms like Fastly and Cloudflare run customer code as Wasm because it starts in microseconds and isolates tenants cleanly. Plugin systems: databases, proxies, and SaaS tools embed a Wasm runtime so users can ship custom logic in any language without the host trusting that code. Envoy filters, Shopify Functions, and several observability agents work this way.

Strengths #

Speed: compiled code runs close to native, far faster than interpreted JavaScript for compute-bound tasks.

Portability: one binary runs anywhere a compliant runtime exists, across operating systems and CPU architectures.

Sandbox security: the capability-based model means a module can do nothing you did not explicitly permit, which is exactly what you want for untrusted plugins.

Language flexibility: Rust, C, C++, Go, and a growing list of others compile to Wasm, so teams pick the language that fits the problem. If you are weighing systems languages for a Wasm target, our pillar on Rust vs Go covers the tradeoffs.

Fast cold starts: a Wasm instance spins up in microseconds rather than the tens or hundreds of milliseconds a container needs. That is a real advantage for serverless and edge, where cold-start latency is often the bottleneck.

Current limits #

Wasm is not magic, and pretending otherwise leads to bad decisions.

No direct DOM access: in the browser you still need JavaScript glue for anything touching the page, and crossing that boundary has a cost. Chatty back-and-forth between JS and Wasm can erase the speed gain.

A maturing ecosystem: the component model and standardized interfaces are still settling. WASI has moved through revisions, and tooling for some languages is further along than others. Go and Rust are comfortable; other languages vary.

Binary size: depending on the language and runtime, a .wasm module can be larger than you expect, especially when it drags in a language runtime or garbage collector. That matters when the module ships over the wire to a browser.

Not a container replacement: Wasm isolates code well, but it does not run arbitrary existing binaries. You compile to it deliberately.

The call we'd make #

Reach for WebAssembly when you have compute-heavy logic that needs to run fast and portably, or when you need to run untrusted code with a strong sandbox and quick startup. Client-side media processing, in-browser engines, edge functions, and plugin systems are the sweet spots, and the pattern is proven in production at Figma, Fastly, and Shopify.

Do not reach for it as a general replacement for JavaScript, containers, or your backend. If your workload is mostly DOM manipulation or I/O glue, the JS boundary tax will eat the benefit. Match the tool to the shape of the problem: heavy, portable, or untrusted compute is where Wasm earns its place.

For concrete scenarios and where each one pays off, see our deeper writeup on WebAssembly use cases. The short version: start with one bounded, compute-heavy piece, measure the boundary cost honestly, and expand from there.