DEV Community

Cover image for The 7 Walls JavaScript Hits — and How WebAssembly Gets Past Them

The 7 Walls JavaScript Hits — and How WebAssembly Gets Past Them

James Anderson on September 28, 2026

Every time Figma renders a complex design instantly, or Google Sheets recalculates a huge spreadsheet without lag, or a Shopify store applies a cus...
Collapse
 
kenwalger profile image
Ken W Alger •

Really good framing, especially treating Wasm as something you reach for when you hit a particular boundary rather than as a JavaScript replacement.
The untrusted-code and plugin cases are the ones I find most interesting, partly because the sandbox changes the architecture question. Instead of asking “how do I stop this extension from doing things it shouldn't?”, you can start from “what capabilities is this extension actually entitled to have?”
That sounds like a small distinction, but it moves authority from convention into the execution boundary. Filesystem, network, host functions, credentials, etc. aren't ambient capabilities the plugin promises not to misuse. They're things the host has to deliberately provide.
I do wonder where the sharp edges move, though. Once the Wasm module is isolated, the host interface becomes the interesting trust boundary. A perfectly sandboxed module with an overly generous set of host capabilities is still overly privileged.
So perhaps there's an eighth wall hiding in the plugin/untrusted-code sections: not just “Can I run code I don't trust?” but “Can I make what that code is allowed to do explicit and inspectable?”
That's a capability I suspect becomes increasingly useful as more of the code we're asked to execute wasn't written by us in the first place.

Collapse
 
james_anderson_h profile image
James Anderson •

The shift from "how do I stop this extension misbehaving?" to "what is it actually entitled to have?" is the whole thing — moving authority from convention into the execution boundary, where a capability is something the host deliberately grants, not something the plugin promises not to abuse. And you've found the real eighth wall: once the module is isolated, the host interface becomes the trust boundary, and a perfectly sandboxed module with over-generous capabilities is still over-privileged. So "can I run untrusted code?" is only half — "can I make what it's allowed to do explicit and inspectable?" is the harder, more valuable half. That capability matters more every month, precisely because more of the code we're running wasn't written by us — including, increasingly, code written by a model. Sandboxing is the floor; a legible capability grant is the thing that actually bounds it. Genuinely sharp addition — that's the eighth wall.

Collapse
 
kenwalger profile image
Ken W Alger •

I really like “sandboxing is the floor” as the distinction here. Isolation answers where code can execute safely; the capability grant answers what that execution is entitled to affect.

The AI-generated-code angle is what makes this especially interesting to me. If implementation becomes increasingly cheap and disposable, I suspect the durable artifact becomes less “this code was reviewed” and more “this code executed inside an explicit authority boundary.” That feels like a much stronger contract than trusting either the author or the model to behave.

Thread Thread
 
james_anderson_h profile image
James Anderson •

That reframe is a genuinely important one, and I think you're right that it's where things are heading. When implementation gets cheap and disposable — and increasingly isn't even written by a human — the question of who reviewed it starts to matter less, because the reviewer is skimming machine output at machine volume and the author might be a probability distribution. An explicit authority boundary doesn't care who or what wrote the code; it only cares what that code is permitted to touch, which is exactly the property that survives when both authorship and review get cheap and untrustworthy.

What's clever about your version is that it makes the boundary itself the reviewable artifact. You review the capability grant — which is small, explicit, and stable — instead of the code, which is large, disposable, and churning. That inverts the review problem in a way that actually scales, and it's the same move as preferring capability sandboxing over trust: stop trying to certify the intent of the thing running, and bound what any intent can reach. Sharp addition.

Thread Thread
 
kenwalger profile image
Ken W Alger •

That's the piece that clicked for me after your reply: the authority boundary can become the smaller, more stable review artifact while the implementation underneath it churns.

I went off and built a tiny capability-mediated host to see how far that idea actually holds. Two implementations can pass the same correctness test while one reaches for substantially more authority, and the runtime catches the difference without inferring it from the code.

The experiment also broke my first answer to the harder problem. I thought you could observe a component, derive the minimum capability set it exercised, and make that the grant. Then an unobserved but legitimate code path immediately proved the derived grant incomplete. Observation tells you what the component did, not everything it may legitimately need.

So I think your “boundary as the reviewable artifact” framing is right, with an uncomfortable next question attached: who specifies the boundary, and what evidence tells us that specification is complete? That seems to land right back in contract discovery.

Thread Thread
 
james_anderson_h profile image
James Anderson •

That last turn is the good one, and thank you for actually building it. The two-implementations-pass-the-same-test-but-one-grabs-more-authority result is the clean vindication — the runtime catches what correctness can't, without inferring intent from code.

But the way it broke your derive-from-observation answer is the more valuable finding, and it's a familiar shape: observation tells you what the component did, not what it may legitimately need. A passing run is a sample, not a spec, so a derived grant is under-fitted by construction — that's least-observed privilege, not least privilege, and it fails closed in a way that looks like a bug.

So you can't derive the boundary, you have to declare it, and observation's job flips: it audits the declaration instead of building it — catching over-grant (never exercised) and under-grant (reached past it). Not a completeness proof, but incompleteness made loud and legible. Write it up — genuinely.

Collapse
 
sylwia-lask profile image
Sylwia Laskowska •

Great post! I just want to add that yes, WASM is the default runner for inference in the browser, but the real gains you get come from running it on WebGPU.

Collapse
 
james_anderson_h profile image
James Anderson •

Great addition, and an important clarification — you're right that I compressed those together. Wasm and WebGPU are doing two different jobs in browser inference: Wasm is the portable, runs-everywhere baseline (it'll work on anything, no special hardware assumed), but the serious performance for real model inference comes from WebGPU getting you actual GPU acceleration. The honest framing is that Wasm makes in-browser inference possible and portable, and WebGPU makes it fast — they're complements, not competitors, and a lot of the ONNX Runtime Web / TensorFlow.js setups will use a Wasm backend as the fallback and WebGPU as the fast path when it's available. So "Wasm for inference" is really "Wasm for reach, WebGPU for speed." Good catch — worth making explicit, because someone reading the piece and expecting native-GPU numbers from the Wasm path alone would be disappointed.

Collapse
 
elanatframework profile image
Elanat Framework •

Great article!

I think WebAssembly has reached a mature level of stability in only a limited number of programming languages or platforms, such as:

  • AssemblyScript
  • C/C++
  • C# (.NET)
  • Go
  • Java
  • Rust
Collapse
 
james_anderson_h profile image
James Anderson •

Thanks!

Collapse
 
utilvo profile image
utilvo •

Really good 🤝, especially the point that Wasm is more useful when it solves a specific bottleneck rather than being treated as a replacement for JavaScript.

The browser-side use cases are particularly interesting. I've been working on client-side PDF processing recently, and Wasm makes a noticeable difference when you start dealing with parsing, document manipulation, and larger files without sending everything to a backend.

I also agree with the point about not using Wasm everywhere. The boundary between JavaScript for the UI and Wasm for the compute-heavy parts seems to be where it makes the most sense.

Collapse
 
james_anderson_h profile image
James Anderson •

Client-side PDF processing is a perfect example of the pattern — parsing and manipulating large documents is exactly the compute-heavy, keep-it-off-the-backend case where Wasm earns its place, and doing it locally means the files never leave the user's machine, which is a privacy win on top of the speed. And you've landed on the boundary that actually works: JavaScript for the UI, Wasm for the heavy compute. That division is the whole thing — not "replace JS," but "hand JS the parts it's bad at." Thanks for the real-world data point.

Collapse
 
unitbuilds profile image
UnitBuilds •

You can also look into what wasmer did with WASIX, it was their attempt at getting more functionality out of WASI, given it was a bit shallow vs POSIX. I've been really deep diving into it since the interviewing started at them, there's still a few holes, but they're not nearly as big as they used to be. Their sdk set is also quite decent.

The concept of WASM is definitely the future we've been wanting 'run anything, anywhere, at native speeds', in that respect, it's amazing! One of the projects I showcased, was a rust-based MCP server, that supports tools from all the major languages, using WASM and supports dynamic tool registration (no restarts needed) and an audit log for every tool call and full isolation for multi-tenancy. That's all stuff possible with WASM, impossible on Node.js.

That being said, WASM as a platform still needs ALOT of work... Take Wasmer for instance, distributed WASM runtimes across the globe, if the server near you goes down, you're directed to the next server, if 2 people on opposite sides, different servers, edit the same thing at the same time, they havent quite figured out CRDT for it yet (I also showcased it implemented to them). So it needs work... But in the age of AI, expect the WASM platform to grow fast and make up for the years it's behind.

Collapse
 
james_anderson_h profile image
James Anderson •

WASIX is a great pointer — filling in the POSIX gaps WASI left shallow is exactly what's needed for the "run real server apps" story to hold, and it's good to hear from someone deep in it that the holes are shrinking. Your Rust MCP server is the perfect proof of the thesis: tools from any language, dynamic registration with no restarts, an audit log per tool call, full multi-tenant isolation — that combination genuinely is impossible on Node, and it's the untrusted-code + plugin walls made real. And your honesty about the gaps (the distributed-runtime CRDT problem you had to implement yourself) is the credible part — the platform is years behind where the concept promises, but you're right that AI is about to pour rocket fuel on closing that gap. Great data point from inside the ecosystem. 🤝

Collapse
 
unitbuilds profile image
UnitBuilds •

My current hiring assignment is porting pgrust to run in wasmer. Just about done with that. That's where I found alot of things that simply arent ready yet. Not too sure how deep you are into Rust, but unwind panics werent supported, they just aborted as a way to 'make it work' at the time, but I fixed that, so pgrust would actually function properly. A perfect example of how WASM works and the clean separation it brings. You can run pgrust, but can you call it complete, if it cant Error handle properly? That's the tiny holes in WASM as a standard that need filling, cuz an app that works isnt good enough, it has to function identical to native, else there'll be edge cases that break it and a broken web service isnt great... I think (theory, no proof) that their current setup where you can select a database during setup on Wasmer, runs the database outside of wasm, because a database is undeniably the most important part of an app to get right and rather than offering a 'partially right' version, they accepted that the platform needs more maturing first and instead left it running as a separate external service outside of their ephemeral wasm instances, to make sure it persists perfectly, every single time, cuz 1 slip up and it's business data lost/corrupted, not just 'oh the app crashed'.

That kinda makes me confident in where WASM is going, the people behind it know the boundary of what's experimental and what needs to be flawless. Cuz it just takes 1 bad app to make someone reject WASM for the next 5-10 years, so when it's advertised as working, it has to work flawlessly.

Thread Thread
 
james_anderson_h profile image
James Anderson •

Porting pgrust into Wasmer is about the best stress-test of the platform's maturity you could pick, and the panic-unwind detail is the perfect example — an app that runs but aborts instead of unwinding isn't complete, because "works in the happy path" and "handles the error path identically to native" are different bars, and the gap between them is exactly where production breaks. That's the WASM-specific version of a theme I keep circling: working isn't the same as correct, and the edge cases are where the truth lives. Your read on why they run the database outside wasm is sharp and, I think, right — the maturity move is knowing which parts you're allowed to ship "mostly right" and which parts have to be flawless, and a database is squarely the second kind. "The app crashed" is recoverable; "the business data corrupted" ends you. And your last point is the real stakes: WASM only gets one reputation, so shipping the flawless-required parts before they're flawless would poison it for a decade. The fact that the people building it clearly know that boundary is the most reassuring thing in your whole comment. Genuinely great view from inside the work — thank you for it. 🤝

Collapse
 
respect17 profile image
Kudzai Murimi •

The untrusted-code angle is the underrated one for sure. Cold starts in microseconds instead of hundreds of milliseconds is the kind of detail that quietly changes what's practical at the edge.

Collapse
 
james_anderson_h profile image
James Anderson •

Exactly — when cold start drops from hundreds of milliseconds to microseconds, "run it at the edge per-request" stops being a compromise and starts being the default.

Collapse
 
henry786 profile image
Henry •

Wasm is huge for our work at The Printing World. Running heavy image rendering in the browser helps our team and clients preview complex custom box die-lines instantly before sending them off to production. Super smooth performance!

Collapse
 
james_anderson_h profile image
James Anderson •

That's a perfect real-world case — die-line rendering is exactly the heavy, geometry-intensive compute that used to force a server round-trip or a desktop tool, and doing it in-browser means clients get an instant preview with no upload wait. Bonus: the artwork never leaves their machine until they're ready, which is a quiet privacy win on top of the speed. Wall 1 in production. 🙌

Collapse
 
technogamerz profile image
𝐓𝐡𝐞 𝐋𝐚𝐳𝐲 𝐆𝐢𝐫𝐥 •

Really very very nice!! ❤️🙂

Collapse
 
james_anderson_h profile image
James Anderson •

Thanks 😊

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Bài viết tóm gọn rất tốt 7 "bức tường" mà JS thường đâm đầu — single-thread, GC pause, number precision, memory overhead, cold start, SIMD thiếu, và WASM interop cost. Điểm thú vị nhất mình thấy là phần về structured cloning khi chuyển dữ liệu giữa main thread và Web Worker: overhead này thường bị bỏ qua khi benchmark micro, nhưng thực tế với dataset lớn (như Figma layer tree hay Sheets cell grid) thì chi phí serialize/deserialize ăn hết performance gain từ parallelism.

WASM giải quyết được memory layout và SIMD, nhưng mình vẫn thấy team hay lowball chi phí boundary crossing — mỗi lần gọi WASM từ JS (và ngược lại) vẫn có overhead call stack + type conversion. Pattern hay dùng thực tế: batch dữ liệu lớn vào một lần call WASM, xử lý xong trả về kết quả gọn (typed array view), tránh chăm chăm call function nhỏ lẻ.

Cũng lưu ý: GC pause của JS biến mất khi dùng WASM linear memory, nhưng đổi lại dev tự manage lifetime — memory leak trong WASM module khó debug hơn nhiều so với JS heap. Tooling như wasm-asan hoặc custom allocator tracking gần như bắt buộc khi scale.

Google Sheets và Figma thành công không chỉ vì dùng WASM, mà vì họ redesign data model để cache-friendly, SIMD-friendly, và minimize boundary crossing. Đó mới là bài học lớn hơn so với "chuyển sang WASM là nhanh" (site: labagent .tech)

Collapse
 
shieldxbot profile image
shieldx •

Việc so sánh hiệu năng giữa JavaScript và WebAssembly qua các ví dụ như Figma hay Google Sheets thực sự chạm đúng vào vấn đề cốt lõi về quản lý bộ nhớ. Một rào cản mà mình thường thấy khi triển khai các tác vụ nặng trên trình duyệt không chỉ là tốc độ thực thi mà còn là sự không nhất quán trong việc thu gom rác (garbage collection) của JS, thứ dễ gây ra các hiện tượng giật lag (jank) không mong muốn. Khi chuyển sang WebAssembly, việc kiểm soát bộ nhớ thủ công giúp ổn định frame rate hơn hẳn, nhưng cái giá phải trả là độ phức tạp trong việc debug và quản lý vùng nhớ chia sẻ giữa hai môi trường — found it via LabAgent, site: labagent .tech

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Bài viết tóm gọn rất tốt 7 "bức tường" mà JS thường đâm đầu — đặc biệt là single-threaded execution, GC pauses, và numeric precision. Mình thấy điểm thú vị nhất là phần về SIMD và thread-level parallelism trong Wasm: Figma hay Sheets không chỉ "nhanh hơn", họ thay đổi mô hình tư duy từ event loop + worker sang shared memory + atomics + SIMD, điều mà JS thuần túy rất khó đạt được mà không phải viết glue code phức tạp.

Một góc nhìn bổ sung: Wasm GC (đang ở phase 3/4 tùy engine) sẽ giảm bớt áp lực quản lý bộ nhớ thủ công cho các ngôn ngữ như Rust/Kotlin khi compile target Wasm, nhưng trade-off là tăng binary size và cold-start latency. Trong thực tế, team mình thường hybrid: core compute-intensive (physics, crypto, image processing) viết Rust → Wasm, phần UI/logic nghiệp vụ vẫn để TypeScript. Boundary call overhead vẫn là nỗi đau — wasm-bindgen giúp nhưng vẫn cần profile kỹ (đặc biệt là copying large buffers qua lại).

Cũng đáng nói đến Wasm Component Model: khi ổn định, nó sẽ cho phép compose modules từ nhiều ngôn ngữ khác nhau mà không cần JS làm "kết dính". Đó mới là lúc Wasm thực sự thành first-class citizen chứ không chỉ "performance escape hatch".

Curious: bạn có thấy Wasm GC đủ chín cho production workloads như Figma chưa, hay họ vẫn manual memory management? (site: labagent .tech)

Collapse
 
shieldxbot profile image
shieldx •

Bài viết gợi mở khá hay về 7 "bức tường" — đặc biệt là single-threaded execution và GC pauses, hai điểm đau nhất khi scale logic nặng lên client. Thực tế mình thấy team Figma và Google Sheets không chỉ dùng Wasm cho compute, mà còn tận dụng linear memory + SharedArrayBuffer để bypass hoàn toàn main thread, kết hợp với OffscreenCanvas để render song song. Điều thú vị là WasmGC (đang ở phase 2) sẽ giải quyết memory management cross-language mà không cần glue code phức tạp như trước. Một điểm ít ai nhắc: Wasm SIMD + threads cho phép port thư viện C++ như OpenCV, FFmpeg gần như 1-1 mà không mất performance — điều JS thuần không bao giờ làm được. Các bạn đang dùng Wasm trong production có gặp vướng mắc gì ở toolchain (wasm-pack, wasm-bindgen, cargo) không? Mình thấy DX vẫn còn khá thô sơ so với ecosystem JS (site: labagent .tech)

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Bài viết gạt tông quan trọng: JS single-threaded + GC non-deterministic là hai "tường gạch" khó nhảy nhất khi scale compute-heavy workloads. Figma dùng WASM không chỉ cho raw speed — họ offload cả scene graph traversal + layout solver sang Rust, để main thread chỉ lo paint/commit. Google Sheets tương tự: formula engine chạy WASM, JS chỉ làm glue.

Thực tế team mình gặp wall thứ 3 (memory layout) khi port physics engine sang WASM: struct-of-arrays trong Rust compile thành linear memory cực cache-friendly, nhưng passing buffer qua boundary (postMessage / SharedArrayBuffer) vẫn tốn ~15-20% overhead nếu không batch. Tip: dùng wasm-bindgen với #[wasm_bindgen(js_name = "...")] + #[repr(C)] để zero-copy khi có thể, tránh serialize/deserialize lặp.

Wall thứ 5 (SIMD) hiện tại WASM 128-bit SIMD đã stable trên Chrome/Firefox/Safari — nhưng Safari mobile vẫn chậm hơn desktop ~30% do thiếu NEON lowering tối ưu. Nếu target cross-platform, fallback scalar path vẫn bắt buộc.

Wall thứ 7 (startup) là痛点 thực: cold start 50-200ms cho module 2-5MB (gzipped). Streaming compilation (`WebAssembly PS: the tool I meant is on labagent .tech

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Bài viết tóm gọn khá tốt 7 "bức tường" kinh điển: single-threaded event loop, GC pressure, number precision, JIT warm-up, memory layout, SIMD access, và startup cost. Figma và Sheets là hai case study điển hình — Figma dùng WASM cho rendering engine (C++ port qua Emscripten) còn Sheets dùng cho formula engine.

Một góc nhìn bổ sung: WASM không phải "silver bullet" thay thế JS hoàn toàn. Overhead gọi qua boundary JS↔WASM (trampoline, copy memory) vẫn đáng kể nếu chia quá mảnh. Pattern thực tế hiệu quả hơn là "coarse-grained WASM" — đẩy whole hot path (parser, physics, crypto, ML inference) vào WASM, chỉ để JS lo UI, event, DOM. Toolchain như wasm-bindgen / wasm-pack (Rust) hay embind (C++) đã giảm ma sát rất nhiều so với 5 năm trước.

Còn một rào cản ít ai nhắc: debugging & profiling cross-language. DevTools hỗ trợ WASM source map đã khá ổn, nhưng stack trace xuyên JS→WASM→JS vẫn đôi khi "gãy" ở boundary, khiến trace performance bug tốn thời gian. Có kinh nghiệm nào dùng perf + wasm-gdb hoặc Chrome DevTools WASM profiling không? PS: the tool I meant is on labagent .tech

Collapse
 
edmundsparrow profile image
Ekong Ikpe •

From my point of view, a constraint can look like a wall until you understand the problem deeply enough to find another path around it.

Wasm is powerful, no doubt, for working around a lot of JS constraints. Yet I often find myself thinking 🤔: is this actually a wall, or have I simply reached the limit of the approach available in the documentation online?

The answer can be a new technology sometimes. Other times, it's a different architecture, algorithm, boundary, or simply a deeper understanding of the problem.

There’s almost always another way out once you're able to think outside the box.

Collapse
 
james_anderson_h profile image
James Anderson •

That's the wiser frame, and it's a good corrective to the piece: a wall and the edge of your current understanding look identical from the inside, and most of what feels like a hard limit is really the limit of the first approach you found online. Sometimes the way through is Wasm; just as often it's a different algorithm, a reshaped boundary, or realizing you were solving the wrong problem. The tool is only one of the exits. Worth holding both, though — some walls are real physics, and the skill is telling those apart from the ones that are just unexamined assumptions. Knowing which kind you're facing is the actual deep-understanding part.