Summary
WebAssembly (Wasm) is a binary instruction format that browsers execute in a sandboxed virtual machine at near-native speed. It is not a JavaScript replacement — it has no DOM access and requires JS glue to interop. The payoff is real for compute-heavy tasks: codecs, image processing, cryptography, and ported C++/Rust libraries.
Jump to the interview angleWebAssembly (Wasm)
A binary instruction format that browsers compile to native machine code and execute inside a sandboxed virtual machine. It targets compute-heavy work — codecs, image processing, cryptography, physics simulations, and ported C++/Rust libraries — where JavaScript's dynamic overhead is measurable. Wasm has no DOM access and no ambient capabilities beyond what JS explicitly imports into it. It is a peer to JS, not a replacement.
When Wasm is worth it on the frontend
- Transcoding video or audio in the browser (ffmpeg.wasm, ~30 MB).
- Encoding or decoding image formats: WebP, AVIF, JPEG-XL via native C libraries.
- Cryptographic operations — SHA-256 or AES over large buffers where JS overhead compounds.
- Physics or simulation loops with tight arithmetic that benefits from predictable native speed.
- Porting an existing C++/Rust codebase (e.g., SQLite via sql.js, pdfium) to avoid a rewrite.
Load and call a Wasm module
Use WebAssembly.instantiateStreaming — it pipes the network response directly into the compiler, saving a parse-then-compile round-trip. The result exposes exported functions as plain JS callables.
// math.wasm exports: add(a: i32, b: i32) => i32
async function loadMath() {
const { instance } = await WebAssembly.instantiateStreaming(
fetch("/math.wasm"),
{} // import object — pass JS functions Wasm can call back
);
// Wasm exports are plain callable functions
const add = instance.exports.add as (a: number, b: number) => number;
console.log(add(40, 2)); // 42
}
loadMath();instantiateStreaming compiles while streaming — no full-download wait. Exports appear on instance.exports; cast them in TypeScript for type safety.
JS↔Wasm boundary cost
Each call across the JS/Wasm boundary marshals arguments. Passing numbers is cheap; passing strings or objects requires encoding them into linear memory. If your code calls Wasm thousands of times per frame, batch the work — pass a buffer of inputs and get a buffer of outputs back in one call, not one call per item.
Wasm vs. plain JavaScript
Pros
- Near-native throughput for tight compute loops — no dynamic type checks mid-loop.
- Deterministic performance: no GC pauses affecting the hot path.
- Ports existing C++/Rust libraries without a full rewrite.
- Sandboxed: no file system or network access beyond explicit JS imports.
Cons
- .wasm bundles must download and compile before first use — adds startup cost.
- No direct DOM or Web API access; every UI mutation crosses the JS boundary.
- Debugging tools are less mature than JS DevTools; source maps help but aren't universal.
- Linear memory management is manual in C/Rust; memory leaks don't surface as JS errors.
- Toolchain complexity: Emscripten or wasm-pack add build steps most JS teams don't know.
Interview angle
Interviewers probe whether you know the JS↔Wasm call overhead and why Wasm is not a general DOM replacement. Name a real use case (e.g., ffmpeg.wasm for video transcoding) and acknowledge the startup cost of large .wasm bundles. Soundbite: "Wasm wins on raw compute; JS wins on DOM and startup time."
Key terms
- Wasm module
- A compiled .wasm binary: a structured binary encoding of a validated module that browsers parse and compile.
- JS↔Wasm boundary
- The call site where JS invokes a Wasm export or vice versa; each crossing incurs marshalling overhead.
- linear memory
- A flat, resizable ArrayBuffer Wasm uses as its heap; JS can read and write it directly.
- Emscripten
- Toolchain that compiles C/C++ to Wasm, generating the JS glue code needed to drive the module.
- wasm-bindgen
- Rust tool that generates JS bindings for Wasm modules compiled from Rust via wasm-pack.