Every few months someone publishes a table showing JavaScript taking 45 milliseconds and WebAssembly taking 8 on the same physics loop, and every few months it turns out nobody can find the code that produced it. We removed ours. What follows instead is what you can actually stand behind: peer-reviewed measurements, a clear picture of where WASM helps, and three ways to start without rewriting your game.

WebAssembly logo
WebAssembly ships in every major browser as a second compilation target alongside JavaScript. Image via Wikipedia.

What WebAssembly Is

WebAssembly (WASM) is a binary instruction format standardized through the W3C. It arrived as a Minimum Viable Product in 2017 and has been extended since with threads, fixed-width SIMD, reference types, exceptions and a garbage-collected tailwind for languages like Kotlin, Java, Dart and Swift.

The execution model is straightforward:

  • Source in C, C++, Rust, Go, C# or AssemblyScript compiles to a .wasm module.
  • The module is fetched, validated, instantiated and compiled by the browser’s engine.
  • JavaScript calls exported functions; the module calls imported functions (console, memory allocation, browser APIs).
  • Both sides share memory through a resizable WebAssembly.Memory — a plain ArrayBuffer you can view, copy and hand to WebGL or WebGPU as a buffer.

It is not a language and not a replacement for JavaScript. It is a second, lower-level place for your code to live.

What the Benchmarks Actually Show

Three results are worth knowing by heart.

1. WASM beats JavaScript — moderately, not dramatically. The definitive independent study, “Not So Fast” (USENIX ATC 2019), found WebAssembly running about 1.3× faster than JavaScript across the SPEC CPU suite. The original WebAssembly announcement reported 34% faster than asm.js in Chrome on small C kernels. Both are real numbers; neither is “three to five times faster.”

2. WASM is slower than native code. The same study measured an average slowdown of 1.55× in Chrome and 1.45× in Firefox against natively compiled binaries, peaking at 2.5× and 2.08×. If your game already ships as a native executable, moving it to the browser is a downgrade in raw throughput — you are trading speed for reach, and usually that trade is worth it.

3. The comparison is workload-dependent. “Understanding the performance of WebAssembly applications” (ISSTA 2021) conducted a systematic study against modern JIT-compiled JavaScript and found mixed results: WebAssembly ahead on some program families, JavaScript ahead on others. Tight numeric loops favor WASM. Code dominated by allocation, string handling and DOM-adjacent work does not.

QuestionAnswer
Is WASM faster than JS?Usually, for compute-heavy loops; sometimes not at all
How much?Often modest — the reliable gain is consistency under load
Is it as fast as native?No: roughly 1.4–1.6× slower on average in published tests
Does it help games?Yes, for physics, AI, procedural generation, audio DSP
Does it help UI code?No — and it makes it harder to write

Where It Belongs in an H5 Game

  • Physics engines — collision detection, broadphase, rigid-body integration. Numeric, allocation-sensitive, runs every frame.
  • AI pathfinding — A*, flow fields, navmesh queries over large grids.
  • Procedural generation — terrain, level layouts, loot tables; often run at load time, where a 3× speed-up is minutes of the player’s life.
  • Audio processing — real-time synthesis, convolution reverb, pitch analysis; the same workloads Web Audio’s worklets were designed for.

Optional

  • Rendering engines — rarely worth it. WebGL and WebGPU already own the GPU, and engine code that only marshals draw calls gains little.
  • Network serialization — useful for binary protocols, pointless when WebSocket and JSON are not your bottleneck.
  • UI and DOM logic. Every DOM call crosses the boundary in the expensive direction. WASM that spends its time calling document is slower than plain JavaScript and far more painful to debug.
  • Simple animations and game flow. requestAnimationFrame, timers and state machines belong where they are easiest to reason about.

Three Ways In

Option 1: AssemblyScript

TypeScript-flavoured syntax, lowest learning curve, compiles from the same tsconfig habit your team already has:

// assembly/index.ts
export function step(dt: f32): void {
  // fixed-timestep simulation body
}

export function fibonacci(n: i32): i32 {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

Best for teams that want WASM today without adopting a second toolchain.

Option 2: Rust + wasm-bindgen

The most active ecosystem, first-class tooling (wasm-pack, wasm-opt), and excellent story for threads and SIMD:

#[wasm_bindgen]
pub fn step(dt: f32, world: &mut World) {
    world.integrate(dt);
    world.resolve_collisions();
}

Best for teams that care about memory layout and want a single language for game logic and native builds.

Option 3: C/C++ + Emscripten

The veteran route, and still the fastest way to bring an existing codebase — Box2D, Bullet, Unreal and Unity’s WebGL builds all travel through it. Output is a .wasm plus a JavaScript loader glue file:

emcc physics.cpp -O3 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -o physics.js

Best when the code already exists in C or C++ and porting beats rewriting.

Practical Recommendations

  1. Migrate gradually. Replace the one module that shows up in your profiler, not the entire engine. A profiler-driven port is a two-weekend project; a big-bang rewrite is a canceled project.
  2. Design the boundary once. Few, coarse calls; state that lives in linear memory; results read back only when something outside the module needs them.
  3. Use SIMD where the math is vector-shaped, but measure — SIMD gains vanish if your loop is actually memory-bound.
  4. For threads, ship the headers. SharedArrayBuffer only exists under cross-origin isolation (COOP + COEP response headers), and discovering this on launch day is a rite of passage nobody needs.
  5. Debug properly. Chrome DevTools supports WASM breakpoints and source maps via DWARF; a module built without debug information is a black box with a stack trace.

Conclusion

WebAssembly is not a performance silver bullet and should never be described as one. It is a well-specified, widely supported second compilation target that gives compute-heavy game code a stable home — while JavaScript keeps the job it is genuinely good at: interfaces, scripting, glue and the endless messy business of the web. Ship both.

Sources