C++ in your Browser. Is WebAssembly worth the effort?
1 C++ for the Masses
C++ is often treated as a legacy programming language. Or something you only need when you are worried about low level system performance. With WebAssembly, that boundary is shifting.
The benefit of Webassembly: free client side compute. You wrap you costly backend logic and ship it to the client.
While I have been familiar with the idea, I wanted to see how this turns out in practice.
Spoiler. Yes compute is free, development time isn’t. You can see the solution here
2 The color contrast grid
I built a small program that arrangers colors in a grid to maximize contrast between neighbors. While simple, the search space is fast and optimization algorithms are difficult to implement.

Besides brute force, I tried hill climbing and simulated annealing. But the problem is actually a little bit more complex than I thought and would need more time.
Therefore, I focused on straightforward C++ improvement: inlining of functions, avoiding to reallocate memory inside loops and lookup tables. The speedups are between 15% to 30%.
3 Getting C++ Data into the Browser Window
WebAssembly offers a way to run high-performance code safely in the browser. Using Emscripten, I compiled the solver into a module and connected it to a small TypeScript UI.
Biggest lesson learned: complex objects cannot cross the boundary by value.
Simply evoking call by value functions of complex objects lead to memory leaks. We can use shared memory buffers to avoid these leaks.
We need
- a fixed buffer in Typescript
- a C++ function to fill the buffer
- another Typescript function to read from it
I implemented this using an additional webworker, as I run several grids at the same time
Buffer declaration in the main.ts
// We first declare the buffer and allocate memory
const canvasRGBBuffers: SharedArrayBuffer[] = [];
const canvasRGBViews: Uint8Array[] = [];
canvasRGBBuffers[canvasIndex] = new SharedArrayBuffer(dim * dim * 3);
canvasRGBViews[canvasIndex] = new Uint8Array(rgbSAB);C++ Function
// In my example I have a global object storing pointers to the algorithms
std::unordered_map<int, std::unique_ptr<Algorithm> > algos;
// This is the actual function that reads the grid
void export_grid_rgb(int id, std::uint8_t *out, int max_len) {
// get grid from the algorithm
auto &algo = *algos.at(id);
auto *grid = algo.getBestGrid();
...
}
// Emscripten Binding to export to typescript
function("export_grid_rgb",
optional_override([](int id, uintptr_t ptr, int len) {
auto *buf = reinterpret_cast<std::uint8_t *>(ptr);
export_grid_rgb(id, buf, len);
})
);Typescript Invocation in the worker
// Receive callback in worker, receives the memory and then forwards the pointer
self.onmessage = async (ev) => {
const {
sab,
} = ev.data;
const createModule = (await import(scriptUrl)).default;
wasmModule = await createModule();
const sharedRGB = new Uint8Array(sab);
const rgbLen = sharedRGB.length;
const wasmRGBPtr = wasmModule._malloc(rgbLen);
wasmModule.export_grid_rgb(canvasIndex, wasmRGBPtr, rgbLen);
}Typescript main
// Send command in main
worker.postMessage({
sab: canvasRGBBuffers[canvasIndex],
});
// Receive callback in main only uses the view on the buffer
worker.onmessage = (ev) => {
...
const rgbView = canvasRGBViews;
drawGridFromRGB(canvas, rgbView, dim);
...
}4 Webassembly Limitations
4.1 Resources
I discovered there are resource limitations.
- 16GB of memory
- Main thread wasm must yield back every 30 odd seconds to avoid dialogs
- Worker threads may be put to sleep if window not in focus
However, that is the size of a small cloud virtual PC. Another aligned technology that allows the usage of gpu is WebGPU. For an intro to both, see this.
Currently GPU access in Webassembly is only possible via the JS-apis. But once direct access is possible, there could be even better performance gains.
4.2 Webhosting
To use the webworkers, we require threads. And to use those we need special CORS headers. These headers are not available on Github Pages, why I needed to host the project on Cloudflare. For hobbiest, that just one extra layer.
5 Summary
As you can see getting something to work in WebAssembly is requires quite a lot of boilerplate code. As the technology is less used LLMs are less of a help. While I used LLMs to create this examples a lot of manual effort was necessary.
For many prototypes, this effort would be better directed into the value adding activities.
This leads to a dilemma: say you have a business idea that would work well if the cloud compute would not be ruining the business case. WebAssembly could make it fly, but to get to working prototype you would be better off developing a FastApi Backend with Python and some pybind bindings for the C++ part.
Some Example Ideas
- Scientific compute with free tier computations
- Client Side ML of SLMs or voice transcription
But I guess that is always the case with infrastructure technology. There is always an upfront cost that needs to be paid.
Source Code is available here
Project is live here