How do you decide which GOARCH targets your Go service will keep supporting?
answer
- cheap to build, expensive to support
- count the devices before arguing
- each target is a permanent constraint
- tiers with dates, not yes or no
- a frozen branch is often the honest middle
basics
~20 sTreat each supported GOARCH as a standing cost: a CI leg, a constraint on every struct and counter, and a bug class you must keep testing for. Decide from fleet telemetry and a dated, tiered support matrix.
solid answer
~50 sEvery architecture in the matrix is a recurring tax, not a one-off port. A 32-bit target means 32-bit `int` and `uintptr`, the 8-byte alignment rule on every 64-bit atomic, a narrower address space, and a CI leg that must build, vet and actually run the suite. So I write the matrix down in tiers — tested in CI, built but untested, unsupported — with a date attached to each demotion. Then I price it against telemetry: how many devices report the 32-bit build, whether the replacement schedule already retires them, whether the hardware could run arm64 instead. The release owner makes the call, but the product owner who wants the old fleet and whoever pays for CI both get to argue, and the honest compromise is usually a frozen maintenance branch rather than shipping every new feature to the old target.
go deeper
You are not expected to set the matrix, but know that the architectures a project supports are a deliberate list and that building for one is not the same as testing it.
Be able to describe what a 32-bit target constrains in day-to-day code — int width, atomic alignment, memory ceilings — so you can explain the cost to whoever is deciding.
Bring the evidence: fleet telemetry per architecture, incident history, the CI legs that exist versus the ones that are advisory, and a concrete proposal for which tier each target belongs in.
Own the matrix and the exit dates. Balance the product's obligation to the old fleet against a permanent constraint on every future struct, name who may overrule you, and offer the frozen-branch compromise before the argument becomes ideological.
## The thing being decided Cross-compiling in Go is famously cheap: set `GOOS` and `GOARCH` and you get a binary. That cheapness is what makes this a genuine judgment call, because the *build* costs nothing and the *support* costs a great deal, and the two are easy to confuse. Adding a 32-bit ARM target to the supported list is not a one-off port; it is a standing commitment that every future change will honour a set of constraints and be tested against them. ## What a 32-bit target actually commits you to - **Type widths.** `int`, `uint` and `uintptr` become 32 bits. Every size, offset, identifier and nanosecond timestamp must be `int64` end to end, and every narrowing conversion becomes a review item. Slices and strings top out around 2^31-1 elements. - **Alignment.** A 64-bit field is only 4-byte aligned, so every 64-bit atomic counter must be a typed atomic or deliberately placed. This constrains struct design forever, including in code that only ever runs on servers, because the same package compiles for both. - **Address space and memory.** A 32-bit process has a much smaller usable address space, so caches, buffers and concurrency limits that are fine on a server need a separate budget on the device. - **Pipeline.** Build, vet and — the expensive part — *run* the tests for that target. Running usually means either a 32-bit GOARCH executed on the amd64 runner or emulation, and emulation is slow enough that people quietly disable it. - **Ecosystem.** Not every dependency is exercised on every port, and a failure there is yours to work around. - **Human cost.** Reviewers must hold the rules in their heads. That cost is invisible in a budget and very visible in incident reviews. ## Getting evidence instead of opinions The argument is usually conducted on sentiment — "we can't drop customers" against "nobody uses it" — and both sides are guessing. Replace it: - **Fleet telemetry.** How many installations report each architecture, and are they growing or decaying? A daemon can report its own `runtime.GOARCH` in a heartbeat, which turns the question into a number. - **Revenue and obligation.** Which contracts or regulatory commitments are attached to those devices, and when do they end? - **Hardware path.** Can the same devices run a 64-bit build? A surprising amount of "32-bit ARM" hardware is 64-bit capable and merely provisioned with a 32-bit image, in which case the decision is an imaging project, not a code one. - **Incident history.** How many production incidents were architecture-specific? That is the ongoing cost, expressed in the currency leadership already understands. - **Pipeline cost.** CI minutes for the extra legs, and the wall-clock they add to every merge. ## Tiers, not a yes/no The useful output is a published matrix with defined tiers, because "supported" without a definition is a promise nobody can keep: - **Tier 1 — tested:** built, vetted and the full suite executed on every merge; regressions block the merge. - **Tier 2 — built:** compiled and vetted every merge, tests run on a slower cadence, no performance commitment. - **Tier 3 — unsupported:** may compile; no promise; bug reports triaged as best effort. Each demotion carries a date announced ahead of time, and the release notes say so. A target that sits in tier 1 in the README and has a red-or-skipped CI leg has already been demoted — you just have not told anyone, and the first bug report is where everybody finds out. ## Who owns it, and who may overrule The release or platform owner sets the matrix, because they hold the pipeline and the release promise. The product owner arguing for the old fleet is a legitimate counterparty with a legitimate case, and so is whoever pays for the CI minutes and the incident load. The compromise that usually survives contact with reality is not "support it fully" or "drop it" but a maintenance branch: the old architecture gets security fixes and critical bug fixes from a frozen line, while new features are built only for the tiers you actually test. That decouples the constraint from the main line of development, which is the real cost you were trying to shed. ## What a strong answer sounds like It names the recurring costs specifically rather than saying "it adds maintenance", it insists on telemetry before the argument, it produces tiers with dates instead of a binary, it says who decides and who may push back, and it offers the frozen-branch middle path. What it does not do is treat cross-compilation being easy as evidence that support is easy.
- What would make you drop a target immediately rather than on a schedule?A constraint the team keeps violating — a misaligned atomic or a truncation reaching devices twice — or a dependency that no longer builds for it. If the leg is red more often than green, it is not a supported target but an aspiration, and saying so out loud is cheaper than the false confidence it buys.
- How do you keep a demoted target honest instead of quietly broken?Give the tier a written definition — built and vetted every merge, tests weekly, no performance commitment — and publish it in the README and release notes. A tier with no CI evidence behind it becomes a support promise nobody can keep, and the first bug report is where you find out.
- How would you make the case in money rather than taste?Two numbers: the recurring cost — CI minutes, the review tax, incidents attributable to word-size or alignment bugs — against the revenue or obligation tied to the devices still on that architecture. If the fleet is a fraction of a percent and mostly out of contract the argument writes itself; if it is a regulated customer you are keeping it, and the discussion becomes how cheaply.
- The devices could run a 64-bit image. Does that end the discussion?It changes it from a code decision to a rollout decision, which is usually the better trade: one imaging campaign against a permanent constraint on every struct and counter. Cost it as a migration with a schedule and a fallback, and keep the 32-bit tier alive only until the fleet telemetry shows the last devices gone.
saying these in an interview costs you the question
- Adds a GOARCH to the matrix with no CI leg
- Drops a target without checking fleet telemetry
- Treats a port as a one-off cost, not recurring
- Says support is easy because cross-compiling is easy
- Lets each engineer decide per pull request
- Promises support for architectures never executed