WebAssembly in 2025: How to Run Code Directly in the Browser

TL;DR

WebAssembly lets you run compiled languages like C, Rust, and Go in browsers at near-native speed. Perfect for performance-critical apps, games, and computational tasks.

I'll be honest - when WebAssembly first came out, I thought it was just another overhyped web standard that would die quietly like so many others. Boy, was I wrong.

After spending the last year actually using WASM in production, I finally understand why people lose their minds over it. It's not just "JavaScript but faster" - it's genuinely changing what's possible in browsers.

What Actually Is WebAssembly?

Think of WebAssembly as a way to run "real" programming languages in your browser. Instead of being stuck with JavaScript for everything, you can write performance-critical code in Rust, C, Go, or whatever, compile it to this special binary format, and run it directly in the browser.

Old way:     Everything → JavaScript → Browser (slow)
WASM way:    C/Rust/Go → WebAssembly → Browser (fast as hell)

The "Assembly" part is misleading - you don't write assembly code. You write normal Rust or C, and the compiler handles the WASM part.

Why I Actually Care Now

It's genuinely fast. I mean stupid fast. I ported a JavaScript image processing function to Rust + WASM and got a 15x speedup. That's not a typo.

You can reuse existing code. Remember that C++ library you loved but could never use in web projects? Now you can.

It just works everywhere. Every modern browser supports it. No polyfills, no feature detection, no bullshit.

Let's Build Something Real

Forget "Hello World" - let's build something that actually shows why WASM matters. We'll create a Mandelbrot set generator because it's computationally intensive and makes pretty pictures.

Setting Up Rust (The Easy Way)

First, get Rust and the WASM tooling:

# Install Rust (if you haven't already)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Install wasm-pack (this is the magic tool)
cargo install wasm-pack

The Rust Code That Actually Does Something

Create a new project:

cargo new --lib mandelbrot-wasm
cd mandelbrot-wasm

Here's the Cargo.toml:

[package]
name = "mandelbrot-wasm"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

And the Rust code (src/lib.rs):

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn mandelbrot_set(
    width: u32,
    height: u32,
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
    max_iter: u32,
) -> Vec<u8> {
    let mut data = vec![0; (width * height * 4) as usize];
    
    for row in 0..height {
        for col in 0..width {
            let x = x_min + (x_max - x_min) * col as f64 / width as f64;
            let y = y_min + (y_max - y_min) * row as f64 / height as f64;
            
            let iterations = mandelbrot_point(x, y, max_iter);
            let color = if iterations == max_iter { 0 } else { iterations as u8 * 5 };
            
            let pixel_index = ((row * width + col) * 4) as usize;
            data[pixel_index] = color;     // Red
            data[pixel_index + 1] = color; // Green  
            data[pixel_index + 2] = color; // Blue
            data[pixel_index + 3] = 255;   // Alpha
        }
    }
    
    data
}

fn mandelbrot_point(cx: f64, cy: f64, max_iter: u32) -> u32 {
    let mut x = 0.0;
    let mut y = 0.0;
    let mut iter = 0;
    
    while x * x + y * y <= 4.0 && iter < max_iter {
        let temp = x * x - y * y + cx;
        y = 2.0 * x * y + cy;
        x = temp;
        iter += 1;
    }
    
    iter
}

Build it:

wasm-pack build --target web --out-dir pkg

Using It in the Browser

Create an index.html:

<!DOCTYPE html>
<html>
<head>
    <title>WebAssembly Mandelbrot</title>
</head>
<body>
    <canvas id="canvas" width="800" height="600"></canvas>
    <button id="generate">Generate Mandelbrot Set</button>
    
    <script type="module">
        import init, { mandelbrot_set } from './pkg/mandelbrot_wasm.js';
        
        async function run() {
            await init();
            
            const canvas = document.getElementById('canvas');
            const ctx = canvas.getContext('2d');
            const button = document.getElementById('generate');
            
            button.onclick = () => {
                console.time('WASM Mandelbrot');
                
                const imageData = mandelbrot_set(
                    800, 600,        // width, height
                    -2.5, 1.0,       // x range
                    -1.25, 1.25,     // y range
                    100              // max iterations
                );
                
                const clampedArray = new Uint8ClampedArray(imageData);
                const imgData = new ImageData(clampedArray, 800, 600);
                ctx.putImageData(imgData, 0, 0);
                
                console.timeEnd('WASM Mandelbrot');
            };
        }
        
        run();
    </script>
</body>
</html>

Serve it with a local server:

python3 -m http.server 8000

Visit http://localhost:8000 and click the button. You should see a beautiful Mandelbrot set render in a few milliseconds.

What Blew My Mind

I wrote the same algorithm in JavaScript to compare performance. The WASM version was consistently 10-15x faster. For computationally heavy stuff, the difference is dramatic.

But it's not just speed. The Rust code feels cleaner and more maintainable than the equivalent JavaScript. Type safety, no weird JavaScript gotchas, and better tooling.

Real Projects Using WASM in 2025

Figma - Their entire rendering engine is WASM. That's why it's so smooth.

Adobe Photoshop - The web version uses WASM to port C++ code directly.

AutoCAD - Browser-based CAD with near-desktop performance.

1Password - Crypto operations in WASM for security and speed.

Lichess - Chess engine analysis powered by Stockfish compiled to WASM.

These aren't toys - they're production applications serving millions of users.

When WASM Actually Makes Sense

Don't use WASM for everything. I made that mistake early on. JavaScript is still better for DOM manipulation, async operations, and most web app logic.

Use WASM when:

  • You're doing heavy computational work (image processing, crypto, simulations)
  • You have existing C/C++/Rust code to port
  • Performance is genuinely critical
  • You need deterministic behavior across platforms

Skip WASM when:

  • You're just building a typical CRUD app
  • Bundle size matters more than performance
  • Your team doesn't know compiled languages

The Gotchas I Learned About

Bundle sizes are chunky. My simple Mandelbrot generator creates a 50KB WASM file. Complex applications can easily hit several MB.

Debugging sucks. Chrome's getting better at WASM debugging, but it's still nowhere near JavaScript tooling quality.

Memory management is your problem. No garbage collector means you need to think about allocation and deallocation.

DOM access is clunky. You can't directly manipulate the DOM from WASM - everything goes through JavaScript bindings.

What's Actually New in 2025

WASI is getting real. WebAssembly System Interface means WASM modules can run outside browsers - on servers, edge functions, even as CLI tools.

Component Model is stabilizing. Better ways to compose and link WASM modules together.

Garbage Collection proposal. Languages like Java and C# will soon compile to WASM with full GC support.

Better tooling everywhere. VS Code, debuggers, profilers - the developer experience keeps improving.

My Honest Take

WebAssembly isn't going to replace JavaScript. It's a specialized tool for specific problems. But when you need it, it's absolutely game-changing.

If you're doing anything computationally intensive in the browser, you owe it to yourself to try WASM. The setup is easier than you think, and the performance gains are real.

Start with Rust if you're new to compiled languages. The tooling is excellent, the community is helpful, and wasm-pack handles most of the complexity for you.