skip to content

You maintain a shared internal library — how do you decide when to raise its go.mod go line, given every consumer inherits that floor?

level: principalimportance: should knowfreq 30%

answer

  1. a floor you impose on others
  2. inventory consumers before features
  3. guard one file, or raise for all
  4. anchor on Go's support window
  5. announce a date, test the oldest release

basics

~20 s

Treat the go line as a compatibility floor you impose on people who did not ask for it. Decide from consumer evidence: who builds this library, what release they can run, and what the raise actually buys. Announce it, ship it in a normal release, and keep testing the oldest release you claim to support.

solid answer

~60 s

The go line is the one field in a library's go.mod that costs other teams work, so I decide it from the consumer side, not the feature side. First, inventory: who imports the library, what Go release their build images pin, and whether anything is genuinely stuck. Second, what the raise buys — a language feature I need everywhere, or one file's worth of convenience I could guard with `//go:build go1.x` instead. Third, the support window: Go supports a release until two newer ones ship, so if my floor is inside that window I am asking for something reasonable, and if the blocking consumer is outside it, their pin is the real problem. Then I announce the target release and a date, ship the raise in an ordinary version with it called out in the release notes, and add CI against the oldest release I claim to support so the claim stays true. The people who can overrule me are the platform team that owns the base images and the largest consumer who cannot move by that date.

go deeper

for a junior

Understand that a library's go line is not a private setting: everyone who imports the library must build with at least that Go release, so changing it affects other teams.

for a middle

Be able to describe both routes — raise the module's go line, or guard the new code in a file with a //go:build go1.x constraint and a fallback — and what each costs to maintain.

for a senior

Show how you would verify before raising: build against the oldest release you claim to support, know which consumers are pinned, and stage the change so no pipeline discovers it by turning red.

for a principal

Own the policy. Set where the floor sits, anchor it on Go's support window, name the date, decide who can delay it and for how long, and make sure an indefinite objection cannot silently become your version policy.

## A one-line change that spends other teams' time Raising the `go` directive in a shared library is unlike almost any other change you can make to it. It does not alter behaviour, it does not break an API, and it will not show up in anyone's tests — it simply makes the library unbuildable for anyone on an older Go release. It is a compatibility floor imposed on consumers who did not ask for one, and that is what makes it a decision with an owner rather than a routine cleanup. ### Start from the consumers, not the feature The wrong sequence is "I want this feature, therefore the go line moves". The right one starts with an inventory: - **Who imports this library?** For an internal library the answer is knowable — search the organisation's repositories. - **What release does each of them build with?** Usually determined by a base image or a CI configuration, not by individual developers. - **Which of them is actually stuck**, and why? "We have not got around to it" is a scheduling problem with a date attached. "Our certified image is frozen until the audit closes" is a real constraint with an end date. "We do not know" is the answer you most need to fix. That inventory converts an argument about preferences into a small number of facts. ### What the raise has to buy Be honest about the benefit. Three cases behave differently: 1. **A language feature the library needs throughout** — new loop semantics, iterators, a builtin used in a dozen places. Guarding is impractical; raising is the honest option. 2. **One file's worth of convenience** — then guard it with `//go:build go1.x` plus a fallback and impose nothing. The cost is two code paths to keep tested forever, which is a real price but a smaller one than blocking a consumer. 3. **Standard-library APIs introduced in a known release.** This one is subtler and argues *for* raising: the go line does not gate stdlib symbols, so calling a new API in a library with an old go line compiles fine for you and fails for consumers with a bare undefined-symbol error. Raising the line converts that into a clear version requirement. Since Go 1.27 the `stdversion` vet check runs during `go test` by default and reports standard-library symbols too new for the module's effective version, so this mismatch is much easier to catch before it ships. ### The support window as a shared reference point Go supports each major release until two newer major releases have shipped. That policy is useful in this conversation because it is external: a floor inside the supported window is a reasonable ask, while a consumer pinned outside it is running something that no longer gets fixes, and their pin is a risk they own independently of your library. Anchoring on the policy turns "you are being aggressive" into "here is the window we are both inside", and it gives the platform team a reason to move that is not about your library at all. ### Making the change cheap to absorb - **Announce the target release and a date** before the commit, not in the release notes afterwards. - **Ship it in an ordinary version**, with the new minimum stated prominently. It is not an API break, so a new major module path is not warranted, but it is a compatibility event and deserves the same visibility. - **State the supported release in the README**, so consumers can plan instead of discovering the floor from a red build. - **Test the claim.** Run the suite against the oldest release you say you support, in CI, on every change. Without that, the support statement is a belief. - **Batch it.** If you own several libraries, raise them together on one date, so consumers do a single image bump rather than one per library per quarter. ### Who can overrule you Two parties, legitimately. The **platform team** owning base images sets what releases exist to be used; if they cannot ship a newer image this quarter, your date moves. The **largest consumer** whose delivery would be blocked can reasonably ask you to wait, and the answer is usually to guard the feature and revisit rather than to argue. What you should refuse is an open-ended veto: the outcome of "not yet" must be a date, otherwise the library's floor is set permanently by whoever upgrades last.

  • A consumer says they cannot move for six months. Do you hold the go line where it is?
    I ask for a date and a reason, then choose. If the feature is optional I guard it behind a per-file build constraint and impose nothing, revisiting on their date. If it is structural, I set the raise for their date and say so publicly. What I do not accept is an open-ended veto, because that hands the floor permanently to whoever upgrades last.
  • Is raising the go line a breaking change that warrants a new major module version?
    No. It breaks no API and no behaviour, so a new major version path would impose a far larger migration than the problem deserves. It is still a compatibility event: state the new minimum in the release notes and the README, and give consumers notice before it lands rather than after.
  • How do you keep your stated minimum honest over time?
    Run the full test suite against the oldest release you claim to support, in CI, on every change — not only against the newest. Otherwise the claim drifts the first time someone uses a newer API or a newer language feature, and the failure surfaces in a consumer's pipeline instead of yours.
  • What is the risk of leaving the go line low while using recent standard-library APIs?
    Consumers on older releases get an undefined-symbol error rather than a version requirement, which is far harder to diagnose from the outside. Raising the line makes the requirement explicit; running the `stdversion` vet check catches the mismatch on your side before it reaches them.

It is the same call as raising the minimum OS version of a shared app: cheap for you to type, and a scheduled migration for everyone who ships on top of it.

saying these in an interview costs you the question

  • Raises the floor because the newest release is out
  • Never inventories who consumes the library
  • Treats an indefinite consumer veto as an acceptable outcome
  • Announces the new minimum only after it ships
  • Claims a supported release without testing against it
  • Uses recent stdlib APIs while keeping the go line low