How do you decide whether to ship a Go library to the browser as wasm when another team owns the bundle budget?
answer
- someone else pays the bytes
- measure before you argue
- divergence risk, not duplicated effort
- price the server-side and rules-as-data options
- decide the fallback and the rebuild owner
basics
~20 sDecide on measured cost against a named risk: reuse is worth megabytes of download only when two implementations disagreeing would itself be a defect. Bring the compressed size, a fallback plan, and an owner for rebuilds.
solid answer
~60 sFrame it as a cost somebody else pays, not as an elegance argument. The cost is concrete: the whole Go runtime is in the module, so even a small library is megabytes uncompressed and hundreds of kilobytes over the wire, plus the toolchain glue, a Go toolchain in the front-end build, one execution thread shared with the page, and a copy at every boundary crossing. The benefit is worth having in exactly one case — when the client and the server disagreeing about a rule would itself be a bug, as with a validator whose verdicts must match. If it is merely “we would rather not write it twice”, that is duplicated effort, not a defect class, and rarely justifies the bytes. So: measure the real compressed delta on the page that will carry it; price the alternatives, including validating server-side behind an endpoint or extracting the rules into data both sides interpret; pick the browser target rather than the WASI one; and decide up front who rebuilds the module when the library changes and what the page does when instantiation fails. Then let the budget owner decide, with numbers.
go deeper
Know that a Go browser module includes the whole runtime and is measured in megabytes, so it is a real cost on page load rather than a free way to reuse code.
Be able to lay out both sides: bundle size, an added toolchain in the front-end build, copying at the boundary and one shared thread, against a single implementation of a rule.
Show you would measure the compressed delta first, keep the module version-locked to the library, and design a fallback for the case where it fails to instantiate.
Own the trade with the team that pays for it: justify the bytes with a named defect class, price the server-side and rules-as-data alternatives, assign the rebuild owner, and record what would make you revisit the call.
## Why this is a decision and not a technique Compiling Go to WebAssembly for a browser is easy. Deciding to make it someone else's problem is not. The engineer proposing it usually owns the Go library and gains code reuse; the engineer who has to accept it owns the page's load performance and pays in bytes, build complexity and a new failure mode. Both are right about their own concerns, which is why the decision needs a shared frame rather than a preference. ## Name the cost precisely A Go WebAssembly module carries the entire runtime, scheduler and garbage collector, so even a thin library lands in the low megabytes uncompressed. It compresses well, and the number that matters is the compressed transfer size on the specific page that will load it, measured, not estimated. Around it sit costs people forget: the toolchain glue script that must be copied from the same Go release and kept in step; a Go toolchain added to the front-end build and CI; a boundary where every string and buffer is copied in both directions; a single execution thread shared with the page, so heavy work needs a Web Worker or it janks the UI; and stack traces that now come from a runtime the front-end team cannot debug. ## Name the benefit precisely There is one benefit that reliably justifies the cost: **divergence would be a defect**. If the browser accepts a document that the server later rejects, or vice versa, that is a user-visible bug that a second implementation makes inevitable over time. Validators, parsers, tariff and pricing rules, and canonicalisation logic all sit in that category. Anything where the client copy is a convenience — a hint, a preview, a nicety — does not: a small hand-written approximation is cheaper and its drift is harmless. So the first question to ask is not “can we” but “what breaks when the two implementations disagree, and how would we find out?” ## Price the alternatives before arguing for this one Three options usually deserve comparison. Run the logic **server-side** behind an endpoint: zero bundle cost, a round trip per check, and it fails when offline — often perfectly acceptable for a form that submits anyway. **Extract the rules into data** — a schema, a table, a small expression language — that both a Go and a JavaScript interpreter read: this keeps one source of truth without shipping a runtime, and is the option most often overlooked. Or **ship the wasm module**. Presenting all three, with the measured numbers, is what makes the conversation a decision rather than a negotiation. ## Get the target right If it ships to a page, it is the browser WebAssembly target with the toolchain's JavaScript glue. The WASI target is for hosts outside the browser — a plugin surface inside another process, an edge runtime — and choosing it for a page is a category error, since browsers do not implement WASI. Both targets are single-threaded, so neither buys parallelism. ## Own the operational contract Saying yes creates obligations that outlive the decision, and they should be written down before anyone merges: - **Who rebuilds** the module when the Go library changes, and how the front end pins a version. A wasm artefact that silently lags the server's rules recreates the divergence you shipped it to prevent — the worst possible outcome, because it looks like it is working. - **What the page does when the module fails to load or instantiate.** The honest answer is fall back to the server check; the dishonest one is skip validation and hope. - **Where the work runs**, main thread or worker, decided by whether the operation can exceed a frame budget. - **Whether the exported surface is asynchronous by default.** It should be, because a callback that blocks inside a call from JavaScript deadlocks the page, and retrofitting that convention across a published surface is expensive. ## Who decides The platform owner proposes; the owner of the page's performance budget can overrule, and that is legitimate. Your job as the proposer is to make the trade legible — the measured compressed cost, the concrete defect class it removes, the alternatives you priced, and the fallback when it does not load. If the answer is still no, the reusable fallback is the rules-as-data option, which usually survives the objection that killed the module. ## The revisit trigger Record what would change the answer: the library growing rules too intricate to keep in sync by hand, a measured divergence incident, or the page acquiring an offline requirement. Decisions like this age, and an explicit trigger is what stops the team relitigating it from memory every year.
- The front-end owner rejects the bundle on size. What do you propose instead?Extract the rules into data — a schema or table both sides interpret — so there is still one source of truth without shipping a runtime, or move the check behind a server endpoint and accept the round trip. Both preserve the property you actually wanted, which is that client and server cannot disagree, and neither costs megabytes.
- What is the worst outcome after shipping the module, and how do you prevent it?A stale module whose rules lag the server's. It looks healthy while silently reintroducing the divergence the module existed to prevent. Prevent it by making the rebuild part of the library's release, pinning and surfacing a version the page can report, and treating a version mismatch as an alert rather than something a human notices later.
- How do you decide whether the module runs on the page's main thread or in a Web Worker?By whether the operation can plausibly exceed a frame budget on the slowest device you support. The target has one execution thread shared with the page, so anything longer janks input and rendering. Measure the worst realistic document, not the average one, and put it in a worker if there is any doubt.
saying these in an interview costs you the question
- Ships wasm because it is interesting, with no measured size
- Assumes the front-end team must absorb whatever the build produces
- Picks the WASI target for a browser page
- Treats the client-side check as a replacement for server validation
- Names no owner for rebuilding the module when the library changes
- Never considers shipping the rules as data instead