What is WebAssembly?
WebAssembly (WASM) is a low-level binary format designed to run in stack-based virtual machines at near-native speeds. It is not a programming language but a compilation target: you can write in C, C++, Rust, Go, AssemblyScript or Python (via Pyodide) and compile to WASM.
Why WASM is revolutionary:
- Performance: 10-100× faster than JavaScript for computationally intensive code
- Portability: same binary runs in Chrome, Firefox, Safari, Node.js, Deno, and servers
- Security: execution in a sandbox with isolated memory
- Multiple languages: the same toolchain works with C, Rust, Go, and more
Primary use cases:
- In-browser image/video editors (Figma, Adobe Photoshop Web)
- Emulators and games (DOSBox, Unity, Unreal Engine 4)
- High-performance cryptography and hashing
- Serverless audio/video processing
- Edge AI/ML (ONNX models in the browser)
Compiling C/C++ to WASM with Emscripten
# Install Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh # Linux/macOS
# emsdk_env.bat # Windows
Example: Math functions in C
// math_utils.c
#include <emscripten/emscripten.h>
#include <math.h>
EMSCRIPTEN_KEEPALIVE
double calculate_hypotenuse(double a, double b) {
return sqrt(a * a + b * b);
}
EMSCRIPTEN_KEEPALIVE
int is_prime(int n) {
if (n < 2) return 0;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return 0;
}
return 1;
}
EMSCRIPTEN_KEEPALIVE
long long fibonacci(int n) {
if (n <= 1) return n;
long long a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b; a = b; b = c;
}
return b;
}
# Compile to WASM
emcc math_utils.c \
-o math_utils.js \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_calculate_hypotenuse","_is_prime","_fibonacci"]' \
-s EXPORTED_RUNTIME_METHODS='["cwrap","ccall"]' \
-s ALLOW_MEMORY_GROWTH=1 \
-O3
Using the module in JavaScript
const Module = await import('./math_utils.js');
await Module.ready;
const hypotenuse = Module.cwrap('calculate_hypotenuse', 'number', ['number', 'number']);
const isPrime = Module.cwrap('is_prime', 'number', ['number']);
const fibonacci = Module.cwrap('fibonacci', 'number', ['number']);
console.log(hypotenuse(3, 4)); // → 5
console.log(isPrime(97)); // → 1 (true)
console.log(fibonacci(40)); // → 102334155 (instant in WASM)
// Benchmark: WASM vs pure JS
function fibJS(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) { [a, b] = [b, a + b]; }
return b;
}
const N = 42;
console.time('WASM');
for (let i = 0; i < 10000; i++) fibonacci(N);
console.timeEnd('WASM');
console.time('JavaScript');
for (let i = 0; i < 10000; i++) fibJS(N);
console.timeEnd('JavaScript');
// WASM is typically 2-5× faster for compute-heavy loops
Compiling Rust to WASM with wasm-pack
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install wasm-pack
cargo new --lib my_wasm
cd my_wasm
# Cargo.toml
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn rle_compress(data: &[u8]) -> Vec<u8> {
let mut result = Vec::new();
let mut i = 0;
while i < data.len() {
let byte = data[i];
let mut count = 1u8;
while i + count as usize < data.len()
&& data[i + count as usize] == byte
&& count < 255
{
count += 1;
}
result.push(count);
result.push(byte);
i += count as usize;
}
result
}
#[wasm_bindgen]
pub fn rle_decompress(data: &[u8]) -> Vec<u8> {
let mut result = Vec::new();
let mut i = 0;
while i + 1 < data.len() {
let count = data[i] as usize;
let byte = data[i + 1];
result.extend(std::iter::repeat(byte).take(count));
i += 2;
}
result
}
#[wasm_bindgen]
pub fn matrix_multiply(
a: &[f64], b: &[f64],
rows_a: usize, cols_a: usize, cols_b: usize
) -> Vec<f64> {
let mut c = vec![0.0f64; rows_a * cols_b];
for i in 0..rows_a {
for j in 0..cols_b {
for k in 0..cols_a {
c[i * cols_b + j] += a[i * cols_a + k] * b[k * cols_b + j];
}
}
}
c
}
wasm-pack build --target web --release
import init, { rle_compress, rle_decompress, matrix_multiply }
from './pkg/my_wasm.js';
await init();
const data = new Uint8Array([1,1,1,2,2,3,3,3,3,4]);
const compressed = rle_compress(data);
const restored = rle_decompress(compressed);
console.log('Compressed:', compressed); // [3,1, 2,2, 4,3, 1,4]
console.log('Restored:', restored); // same as original
Using WASM from Python with wasmer
# pip install wasmer wasmer-compiler-cranelift
from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler
def load_wasm(wasm_path):
"""Load and return a WASM module instance."""
store = Store(engine.JIT(Compiler))
with open(wasm_path, 'rb') as f:
bytecode = f.read()
module = Module(store, bytecode)
instance = Instance(module)
return instance
def wasmer_example():
"""Run a WAT (WebAssembly Text) module directly."""
wat = '''
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)
(export "add" (func $add))
(func $factorial (param $n i64) (result i64)
(local $result i64)
i64.const 1
local.set $result
(block $break
(loop $loop
local.get $n
i64.const 1
i64.le_s
br_if $break
local.get $result
local.get $n
i64.mul
local.set $result
local.get $n
i64.const 1
i64.sub
local.set $n
br $loop))
local.get $result)
(export "factorial" (func $factorial)))
'''
store = Store(engine.JIT(Compiler))
module = Module(store, wat)
instance = Instance(module)
add = instance.exports.add
factorial = instance.exports.factorial
print(f"3 + 4 = {add(3, 4)}") # → 7
print(f"10! = {factorial(10)}") # → 3628800
print(f"20! = {factorial(20)}") # → 2432902008176640000
wasmer_example()
Pyodide: Full Python in the Browser via WASM
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
</head>
<body>
<script type="text/javascript">
async function main() {
const pyodide = await loadPyodide();
await pyodide.loadPackage(['numpy', 'pandas']);
pyodide.runPython(`
import numpy as np
import pandas as pd
data = np.random.randn(1000)
print(f"Mean: {np.mean(data):.4f}, Std: {np.std(data):.4f}")
df = pd.DataFrame({'x': np.linspace(0, 10, 100),
'y': np.sin(np.linspace(0, 10, 100))})
print(f"DataFrame: {df.shape}")
`);
// Convert Python data to JavaScript
const arr_py = pyodide.runPython('list(range(10))');
console.log('Python→JS:', arr_py.toJs());
}
main();
</script>
</body>
</html>
When to Use WASM vs Alternatives
| Use Case | WASM | JS/TS | Native Python | Node.js C++ addon |
|---|---|---|---|---|
| Heavy algorithms in browser | ✅ Ideal | ⚠️ Slow | ❌ | ❌ |
| Reuse C/C++/Rust code | ✅ Ideal | ❌ | ❌ | ⚠️ Complex |
| Fast web apps | ✅ | ✅ | ❌ | ❌ |
| High-performance server | ✅ (WASI) | ⚠️ | ✅ | ✅ |
| Portability without install | ✅ Maximum | ✅ | ❌ | ❌ |
| Debugging and profiling | ⚠️ Hard | ✅ Easy | ✅ Easy | ⚠️ |
Conclusion
WebAssembly has evolved from a curiosity to critical infrastructure: Figma, AutoCAD Web, Google Earth, Adobe Photoshop Web and dozens of productivity applications use it in production. For Python, the main paths are wasmer/wasmtime for running WASM modules in server scripts, and Pyodide for running full Python in the browser. For maximum performance, Rust + wasm-pack remains the recommended combination.
Related conversions
Frequent conversions across the catalogue: