🧩 WebAssembly Concepts
For years the browser could run exactly one language: JavaScript. WebAssembly changes that. It is a compact, portable binary format that lets code written in C, C++, Rust, Go, or AssemblyScript run inside the browser at close to native speed — right alongside your JavaScript, not in place of it. In this lesson you'll learn what WebAssembly really is, how source code becomes a .wasm binary, and how that binary runs safely inside a sandboxed virtual machine.
Week 14 · Day 5 (Friday: WebAssembly Basics) · Lecture 1
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Define WebAssembly as a portable binary instruction format and compile target
- Trace the pipeline from a source language, through a compiler, to a
.wasmbinary running in the browser VM - Explain the stack-based execution model, linear memory, and why WASM is sandboxed and deterministic
- Read a small WebAssembly text-format (
.wat) module and match it to its binary - Describe how WASM complements JavaScript and decide when a task is a good fit
- Name the main source languages and toolchains that target WebAssembly
Estimated Time: 55 minutes
Practice: Hand-trace a .wat add function and classify tasks as WASM-friendly or not.
In This Lesson
What Is WebAssembly?
WebAssembly (usually shortened to WASM) is a portable binary instruction format for a stack-based virtual machine. That's a mouthful, so let's unpack it one word at a time:
- Binary — it ships as compact bytes (
.wasmfiles), not human-readable text, so it downloads fast and parses quickly. - Instruction format — it is a low-level list of operations (add these two numbers, load this byte from memory) very close to what a CPU understands.
- Portable — the same
.wasmruns identically in every modern browser and in server runtimes, regardless of the operating system or chip. - Compile target — you don't usually write WASM by hand. You write C, C++, Rust, Go, or AssemblyScript and a compiler produces the
.wasmfor you.
📖 Analogy: WASM is a universal shipping container
Before standardized shipping containers, every port loaded cargo its own way. The container changed everything: pack goods once, and any crane, truck, or ship in the world can move the box without caring what's inside. WebAssembly is that container for code — a compiler packs your program into a standard box, and any browser or WASM runtime can execute it without knowing (or caring) that it started life as Rust or C++.
What WebAssembly is not
WebAssembly is not a replacement for JavaScript, and it is not a programming language you'll write day to day. It has no built-in access to the DOM, no garbage collector of its own (in its core form), and no way to make a network request on its own. It is a fast, safe execution engine that JavaScript drives. The two are partners: JS orchestrates the page and the browser APIs; WASM does the heavy number-crunching.
✅ The one-sentence definition
WebAssembly is a compact, portable binary format that runs compiled code (from C, C++, Rust, and others) at near-native speed inside the browser's sandboxed virtual machine, working alongside JavaScript.
The Compile Pipeline
The single most important mental model for WebAssembly is the pipeline: source languages → a compiler → a .wasm binary → the browser's virtual machine. Once you can picture this flow, everything else clicks into place.
.wasm binary; the browser's virtual machine loads and executes it.The key insight: the "hard work" of translating human-friendly source into machine-friendly instructions happens once, at build time. JavaScript, by contrast, is delivered as text and must be parsed and just-in-time compiled by the browser every time the page loads. WebAssembly arrives pre-digested, which is a big part of why it starts fast and runs fast.
A brief history
WebAssembly grew out of earlier experiments to run fast code in the browser — Mozilla's asm.js (a strict, optimizable subset of JavaScript) and Google's Native Client. In 2015 the major browser vendors formed a community group, shipped a minimum viable product in 2017, and by 2019 WebAssembly was an official W3C recommendation with support in every mainstream browser. It is now a stable, standardized part of the web platform.
The Sandboxed Virtual Machine
When the browser loads a .wasm module, it runs the instructions on a small, well-defined virtual machine (VM). Understanding a few of its properties explains both why WASM is fast and why it is safe.
A stack-based machine
WebAssembly is stack-based: most instructions push values onto an operand stack or pop values off it. To add two numbers, you push the first, push the second, then run i32.add, which pops both and pushes the sum. This model is simple to validate and easy for the browser to translate into real machine code.
Linear memory: one big array of bytes
A WASM module gets a single, contiguous block of memory called linear memory — essentially a resizable ArrayBuffer. All of the module's data lives here, and crucially, JavaScript can see the exact same bytes through a typed array. This shared buffer is how larger data (like an image) crosses between the two worlds, a topic the next lesson explores in depth.
Sandboxed and deterministic by design
- Sandboxed — a module can only touch its own linear memory. It cannot read arbitrary browser memory, reach the file system, or call the network unless JavaScript explicitly hands it a function to do so. A malicious or buggy
.wasmis contained. - Structured control flow — there are no arbitrary "jump to any address" instructions. Branches are structured (blocks, loops, ifs), which lets the browser validate a module quickly and prove it is well-formed before running a single instruction.
- Deterministic — given the same inputs, a WASM module produces the same outputs on every machine, with the same integer and floating-point behavior. There are no surprising platform differences.
💡 Why the sandbox matters
The web's whole security model rests on running untrusted code safely. WebAssembly was designed inside that model from day one: it validates fast, cannot escape its memory, and reaches the outside world only through explicit imports that JavaScript provides. You get native-like speed without giving up the browser's safety guarantees.
Binary & Text Formats
WebAssembly has two faces of the same thing. The binary format (.wasm) is what ships over the network. The text format (.wat, "WebAssembly Text") is a human-readable equivalent used for learning, debugging, and inspection. Tools like wat2wasm and wasm2wat convert between them.
The text format (.wat)
Here is a complete module that exports one function, add, taking two 32-bit integers and returning their sum. Read it top to bottom like a recipe:
(module
;; Define a function named $add that takes two i32 params
;; and returns one i32 result.
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a ;; push parameter a onto the stack
local.get $b ;; push parameter b onto the stack
i32.add) ;; pop both, push a + b
;; Make $add callable from JavaScript under the name "add".
(export "add" (func $add)))
Notice the stack machine at work: local.get pushes a value, and i32.add consumes the top two values and leaves the result. The final value on the stack becomes the return value.
The binary format (.wasm)
Compiled to binary, that same module is just a stream of bytes. Viewed as hexadecimal it looks like this — unreadable to us, but tiny and fast for the browser:
00 61 73 6d 01 00 00 00 ; "\0asm" magic + version 1
01 07 01 60 02 7f 7f 01 ; Type section: (i32, i32) -> i32
7f 03 02 01 00 07 07 01 ; Function + Export sections
03 61 64 64 00 00 0a 09 ; "add" export, code section begins
01 07 00 20 00 20 01 6a 0b ; body: local.get 0, local.get 1, i32.add, end
The first eight bytes are always the same: the magic number \0asm followed by the version. After that come sections that describe the module's types, functions, memory, imports, exports, and the actual code bodies. You never write these bytes by hand — the compiler does — but seeing them demystifies what "a binary format" really means.
⚠️ You rarely touch .wat in real projects
The text format is a learning and debugging aid, not your daily workflow. In practice you write Rust or C++, run a build command, and get a .wasm plus a JavaScript "glue" file. Reading a little .wat is like reading a little assembly — invaluable for understanding, unnecessary for shipping.
WASM Alongside JavaScript
The whole point of WebAssembly on the web is that it complements JavaScript. Each does what it is best at. JavaScript owns the DOM, events, fetch, and the general glue of your app. WebAssembly owns the CPU-heavy inner loops. They call each other across a well-defined boundary.
DOM, events, fetch"] -->|"calls exported functions"| WASM["WASM module
fast compute"] WASM -->|"returns numbers"| JS WASM -->|"calls imported functions"| IMP["Imported JS functions"] IMP --> JS JS -->|"reads and writes"| MEM["Shared linear memory"] WASM -->|"reads and writes"| MEM
Two directions of interop matter:
- JS calls WASM — after loading a module you get an
instance.exportsobject. Its exported functions are ordinary-looking JavaScript functions that happen to run compiled code. - WASM calls JS — you can hand the module an import object full of JavaScript functions. Since WASM has no DOM access of its own, this is how it reaches back out (for example, to log a value or touch the page).
// The simplest possible load-and-call in the browser.
// instantiateStreaming compiles the module *while it downloads*.
const { instance } = await WebAssembly.instantiateStreaming(
fetch('add.wasm')
);
// instance.exports.add is our compiled function.
console.log(instance.exports.add(40, 2)); // 42
💡 The boundary has a cost
Plain numbers (i32, i64, f32, f64) cross the JS/WASM boundary almost for free. Strings, arrays, and objects do not — they must be copied into or read out of linear memory, which is called marshalling. Cheap numbers and expensive structured data is the central trade-off you'll design around, and the next two lessons dig into it.
Source Languages & Tooling
You reach WebAssembly through a language you already know or can learn quickly. The most common paths:
| Language | Toolchain | Best for |
|---|---|---|
| C / C++ | Emscripten | Porting large existing codebases and libraries |
| Rust | wasm-pack + wasm-bindgen | New, memory-safe, high-performance modules |
| AssemblyScript | asc compiler | Web devs — a TypeScript-like syntax compiling to WASM |
| Go | Official GOOS=js GOARCH=wasm target | Reusing Go logic (larger binaries) |
Rust: the ergonomic modern path
Rust has become the flagship WebAssembly language because wasm-bindgen automates the tedious marshalling. You write ordinary Rust; the tooling generates the JS glue that passes strings and structs across the boundary for you.
// lib.rs — a Rust function exported to JavaScript.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
if n <= 1 {
return n;
}
let (mut a, mut b) = (0u32, 1u32);
for _ in 2..=n {
let next = a + b; // iterative — no deep recursion needed
a = b;
b = next;
}
b
}
// Build with: wasm-pack build --target web
// wasm-pack emits a .wasm plus a JS module that wraps it.
import init, { fibonacci } from './pkg/my_module.js';
await init(); // loads and instantiates the .wasm
console.log(fibonacci(10)); // 55
C with Emscripten
Emscripten is the veteran toolchain for C and C++. It can compile an entire game engine's worth of code, emitting a .wasm plus a JavaScript file that sets up memory and glue:
# fib.c compiled to WASM:
# emcc fib.c -o fib.js -sEXPORTED_FUNCTIONS=_fibonacci -sEXPORTED_RUNTIME_METHODS=cwrap
#
# Then in the browser, load the generated fib.js which sets up
# Module, and call the wrapped function:
const fibonacci = Module.cwrap('fibonacci', 'number', ['number']);
console.log(fibonacci(10)); // 55
✅ Which should you pick?
Porting existing C/C++? Reach for Emscripten. Writing something new and want safety plus great tooling? Choose Rust + wasm-pack. Coming straight from web development and want the gentlest ramp? Try AssemblyScript, whose syntax will feel like TypeScript.
Beyond the Browser (WASI)
WebAssembly was born in the browser, but its portability and sandbox make it valuable everywhere. The WebAssembly System Interface (WASI) is a standard that lets a .wasm module request system capabilities — files, clocks, randomness — in a controlled, capability-based way outside the browser.
Because a WASI runtime grants only the capabilities you explicitly allow, WebAssembly has become popular for:
- Serverless and edge computing — modules start in microseconds and are isolated, so platforms run untrusted customer code close to users.
- Plugin systems — applications embed a WASM runtime to run third-party extensions safely inside a sandbox.
- Portable server workloads — one binary runs on any OS or chip with a compatible runtime.
📖 The takeaway
"WebAssembly" no longer means "assembly for the web" exclusively. It is becoming a universal, secure, portable compilation target for the browser, the server, the edge, and embedded devices — anywhere you want to run fast, sandboxed code.
Practice & Quiz
🏋️ Exercise 1: Trace the stack machine
Goal: Given the .wat below, work out what value the exported function returns when called as calc(7, 3). Track the operand stack instruction by instruction.
(module
(func $calc (param $a i32) (param $b i32) (result i32)
local.get $a ;; stack: [a]
local.get $b ;; stack: [a, b]
i32.sub ;; stack: [a - b]
local.get $b ;; stack: [a - b, b]
i32.mul) ;; stack: [(a - b) * b]
(export "calc" (func $calc)))
💡 Hint
Each local.get pushes a value. i32.sub pops the top two and pushes second_from_top - top. i32.mul pops two and pushes their product. Substitute a = 7, b = 3 and compute step by step.
✅ Solution
The function computes (a - b) * b. With a = 7, b = 3: (7 - 3) * 3 = 4 * 3 = 12. In JavaScript, instance.exports.calc(7, 3) returns 12.
🏋️ Exercise 2: WASM-friendly or not?
Goal: For each task, decide whether WebAssembly is likely to help or is a poor fit, and say why in one line.
- Applying a Gaussian blur to a 4000×3000 photo
- Toggling a CSS class when a button is clicked
- Decoding a custom video codec frame by frame
- Reading three form fields and validating an email address
💡 Hint
Ask: is this task CPU-heavy with lots of arithmetic over big buffers of numbers, or is it DOM-heavy / trivial? Remember the boundary cost — tiny jobs pay overhead without a payoff.
✅ Solution
- 1. Helps. Heavy per-pixel arithmetic over millions of numbers — WASM's sweet spot.
- 2. Poor fit. Pure DOM work; WASM has no DOM access and the interop overhead dwarfs the task.
- 3. Helps. Tight numeric inner loops on large buffers — classic codec territory.
- 4. Poor fit. Trivial, string-heavy, DOM-adjacent work; plain JavaScript is simpler and just as fast.
🎯 Quick Quiz
Question 1: Which statement best describes WebAssembly?
Question 2: Why can WebAssembly not directly change the page's DOM?
Question 3: Which data crosses the JS/WASM boundary most cheaply?
Best Practices & Pitfalls
✅ Do
- Reach for WASM when work is CPU-heavy: image/video, crypto, codecs, physics, simulation
- Keep JavaScript in charge of the DOM, events, and fetch — let it orchestrate
- Design your API so numbers cross the boundary and bulk data lives in shared memory
- Serve
.wasmwith theapplication/wasmMIME type so streaming compilation works - Learn a little
.watto understand and debug, then let the compiler produce the binary
❌ Don't
- Reach for WASM for trivial or DOM-heavy tasks — the interop overhead can exceed the gain
- Expect WASM to touch the DOM or network on its own — it needs JS imports
- Assume WASM is always faster than JavaScript — measure before you commit
- Hand-write
.wasmbytes — use a real toolchain (Emscripten, wasm-pack, AssemblyScript)
⚠️ "Near-native," not "native"
WebAssembly runs fast, but it still lives inside the browser's VM and its sandbox. Expect roughly native-adjacent performance for compute, not literal bare-metal speed — and remember that crossing into and out of WASM costs a little every time.
Summary
🎉 Key Takeaways
- WebAssembly is a portable binary instruction format and a compile target for C, C++, Rust, Go, and AssemblyScript
- The pipeline is source → compiler →
.wasm→ browser VM; the hard translation happens once, at build time - It runs on a sandboxed, stack-based VM with linear memory, is deterministic, and validates fast
- It has both a binary format (
.wasm, shipped) and a text format (.wat, for humans) - WASM complements JavaScript: JS drives the DOM, WASM crunches numbers — numbers cross cheaply, structured data needs marshalling
- With WASI, WebAssembly runs beyond the browser on servers, the edge, and in plugin systems
📚 Additional Resources
- WebAssembly.org — the official site
- MDN — WebAssembly Concepts
- The Rust and WebAssembly Book
- AssemblyScript — Introduction
🚀 What's Next?
Now that you understand what WebAssembly is, the next lesson gets hands-on: Using WASM in Web Apps — loading modules with instantiateStreaming, wiring up imports and exports, and marshalling strings and image data across shared linear memory.
🎉 Solid foundation!
You can now explain WebAssembly to a teammate and reason about when it belongs in a project. Time to put a module to work.