skip to content

Language Version Policy

How a program pins the Go it is written in: the promise that keeps old code compiling, the go line that gates newer syntax, GODEBUG for behaviour changes, and toolchain choice.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

19

What does the `go 1.22` line in a module's go.mod declare, and is it the Go version you must have installed?

level: juniorimportance: must knowfreq 60%

answer

  1. one line, two separate jobs
  2. a floor, not the compiler you have
  3. minimum release plus language version
  4. newer compiler, still the old semantics
  5. go mod edit -go= raises it deliberately

basics

~20 s

The go line names the minimum Go release the module needs and the language version its files compile under. It is a floor, not the exact compiler you run: any newer Go release builds the module fine, and still applies the older language version.

solid answer

~40 s

The `go` directive in go.mod does two jobs. It is a minimum requirement: since Go 1.21 the go command treats a go line newer than the release doing the build as an error rather than compiling anyway. And it sets the language version for every file in the module, so language features introduced after that version are rejected even when a much newer release is compiling. It says nothing about which compiler you happen to have installed — Go 1.27 will happily build a `go 1.22` module and will keep giving it Go 1.22 language semantics. Raising it is a deliberate edit: `go mod edit -go=1.24`, or `go mod tidy -go=1.24` if you also want requirements reconciled in the same step.

code

mod · 5 lines
mod
module example.com/internal/telemetry

go 1.22

require example.com/other v1.4.0

go deeper

for a junior

Be ready to say the two things the line means in one breath: minimum Go release required, and the language version the module's files compile under. Knowing it is not the compiler you installed is the whole point.

for a middle

Explain the mechanics: which kinds of change are gated on it (language features) versus which are not (standard-library symbols, which come from the installed release), and name the commands that change it.

for a senior

Show the production angle. A go line that is too low hides new-API usage behind confusing undefined-symbol errors for consumers; one that is too high blocks builds. Know how to read every module's go line before you upgrade anything.

for a principal

Own the framing that the directive is a compatibility floor imposed on everyone downstream. Argue where the floor should sit for the modules your organisation publishes, and who is consulted before it moves.

## The one line, and its two jobs Every module has a `go.mod` file, and near the top it carries a directive like: ``` module example.com/internal/telemetry go 1.22 ``` That single line does two different things, and most confusion about it comes from collapsing them into one. ### Job 1: a minimum required release The go line says "this module needs Go 1.22 or later". Since **Go 1.21** that is a hard requirement: if the release doing the build is older than the go line, the go command reports an error naming the required version instead of attempting the compile, unless it is able to run a newer Go release instead. Before Go 1.21 the line was advisory — the toolchain compiled anyway and only mentioned the module's go version as a note appended to whatever compile error you got, which produced very confusing bug reports ("undefined: something" plus a version hint at the bottom). It is a **floor**, not a pin. Nothing stops a newer release from building the module; the whole point is that a Go 1.27 install can build modules whose go lines say 1.16, 1.21 or 1.25. ### Job 2: the language version The go line also sets the **language version** used to compile every file in the module. Language changes in Go are gated on it, so a module with a low go line does not silently pick up new syntax and semantics when someone builds it with a newer compiler. Concretely: - `min`, `max` and `clear` builtins arrived in **Go 1.21**; - per-iteration loop variables and `for range` over an integer arrived in **Go 1.22**; - range-over-function iterators arrived in **Go 1.23**; - generic type aliases arrived in **Go 1.24**. A module whose go.mod says `go 1.20` gets none of those, no matter how new the installed compiler is. Reach for one and the compiler rejects it with a message that names the language version it was given and points you back at go.mod. This is what makes the go line a *feature gate*. It exists so that upgrading your compiler is a safe, boring operation: the language your existing code is compiled as does not move until you move it. ### What the go line is not - **Not the compiler version you installed.** Those are independent; the installed release just has to be at least as new. - **Not a version of your dependencies.** Each dependency records its own go line and is compiled at its own language version. - **Not necessarily a per-file setting.** It applies module-wide, although a `//go:build go1.x` constraint can raise the language version for a single file. ### A standard-library subtlety worth knowing The go line gates *language* features. Standard-library **symbols** come from whatever release compiles the code, so a `go 1.20` module that calls a function introduced in Go 1.24 will compile happily on a Go 1.24 install and fail with a plain "undefined" error for anyone on Go 1.22 — a much worse error than a version requirement. Since **Go 1.27** `go test` runs the `stdversion` vet check by default, which reports uses of standard-library symbols that are too new for the module's effective Go version, turning that trap into a clear diagnostic. When you start depending on new stdlib APIs, raise the go line so consumers get an honest version error instead. ### Changing it `go mod edit -go=1.24` rewrites just the directive. `go mod tidy -go=1.24` sets it and reconciles the module's requirements to what that version implies. Either way, raising it is a decision with other people attached: every consumer of your module inherits the floor.

  • Does building a `go 1.20` module with Go 1.27 give it any Go 1.22 language features?
    No. The language version comes from the module's go line, so the code compiles under Go 1.20 rules: no per-iteration loop variables, no `for range` over an integer, no `min`/`max` builtins. The newer compiler only means the module is allowed to build at all; it does not upgrade the semantics.
  • What changed about the go directive's status in Go 1.21?
    It became a hard minimum. From Go 1.21 on, a go line newer than the release doing the build is reported as a version error rather than being treated as advice. Before that the toolchain compiled anyway and only appended a note about the required version when compilation happened to fail.
  • What is the difference between `go mod edit -go=1.24` and `go mod tidy -go=1.24`?
    `go mod edit -go=1.24` mechanically rewrites the directive and touches nothing else. `go mod tidy -go=1.24` sets the same directive and then reconciles the module's requirement list to what that language version implies, rewriting go.mod and go.sum as needed. Use edit for a surgical change, tidy when you want the file consistent afterwards.

It is like a document saying "requires Word 2016 or later, formatted for Word 2016": a newer editor opens it, but it is still laid out by the older rules until someone re-saves it.

saying these in an interview costs you the question

  • Says the go line pins which compiler version is used
  • Thinks a newer Go install automatically enables newer language features
  • Believes the go line is only documentation with no effect
  • Confuses the go directive with a dependency's version
  • Assumes every dependency compiles at the main module's language version
open as a page

Your machine has Go 1.24 installed and go.mod says `go 1.27` — why does `go build` still succeed?

level: juniorimportance: must knowfreq 50%

basics

~20 s

Because GOTOOLCHAIN defaults to auto: the go line in go.mod is a minimum, not a maximum, so the go command downloads a newer Go toolchain as a module and re-executes your build with it instead of failing.

open as a page

What does the GODEBUG environment variable control, and how is its value formatted?

level: juniorimportance: should knowfreq 36%

basics

~20 s

GODEBUG carries comma-separated name=value settings, such as panicnil=1, that switch particular runtime and standard-library behaviours back to an older Go release's semantics. A Go binary reads them when it starts, so no rebuild is needed.

open as a page

What does the Go 1 compatibility promise guarantee when you move to a newer Go toolchain?

level: juniorimportance: should knowfreq 38%

basics

~20 s

Go 1 promises that source building and running correctly under one Go 1.x release keeps building and running under later ones. It binds the language specification and the documented standard-library API, so upgrading is normally just a recompile.

open as a page

A module with `go 1.21` in go.mod is built by Go 1.25 — does `for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() }` share one `i`?

level: middleimportance: should knowfreq 42%

basics

~20 s

Yes, one shared i. Per-iteration loop variables arrived in Go 1.22 and are gated on the module's go line, not on the installed release. A go 1.21 line keeps Go 1.21 semantics, so all three goroutines close over the same variable.

open as a page

Which mechanisms let a Go module ship its own GODEBUG defaults instead of relying on the environment?

level: middleimportance: should knowfreq 28%

basics

~20 s

Two declarations bake GODEBUG settings into a build: a godebug line in go.mod (Go 1.23) and a //go:debug comment before the package clause of a main package or test file (Go 1.21). The GODEBUG environment variable still overrides both.

open as a page

What does the Go 1 compatibility promise explicitly leave itself free to change?

level: middleimportance: should knowfreq 45%

basics

~20 s

The promise excludes unspecified behaviour such as map iteration order and error message text, plus security fixes, bug fixes, tool behaviour and performance. Additive API changes are allowed too, and adding a struct field breaks unkeyed composite literals.

open as a page

In go.mod, what does the `toolchain go1.27.0` directive do that the `go` line does not?

level: middleimportance: should knowfreq 38%

basics

~20 s

The go line states the minimum Go version a module requires. The toolchain line names which release to switch to when a switch happens, must not be lower than the go line, and is ignored when switching is disabled.

open as a page

What do the GOTOOLCHAIN values auto, local, path and go1.27.0 each tell the go command to do?

level: middleimportance: should knowfreq 45%

basics

~20 s

GOTOOLCHAIN=auto uses the installed Go unless the module needs newer, then downloads it. local never switches and errors instead. path looks for an installed go1.x binary rather than downloading. A version name like go1.27.0 pins that exact toolchain.

open as a page

After a dependency upgrade, go build fails saying a module requires go >= 1.24 while CI runs Go 1.22 — why, and what are your options?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A dependency you pulled in declares a go line of 1.24, and every module contributing packages to the build must be buildable by the release doing the build. The upgrade may also have raised your own go directive to match. Read the go.mod diff, then either move CI forward or step the dependency back.

open as a page

How do you prove a service still needs its GODEBUG opt-out after a Go upgrade?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Read the runtime's /godebug/non-default-behavior counter for that setting: it increments only when the program actually took the old code path. Zero across a full traffic cycle, including rare batch paths, is the evidence to remove the setting.

open as a page

A Go service untouched for years is rebuilt on a modern toolchain and one test now fails: how do you triage it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Assume the code relied on something Go never promised. Read the compatibility notes for each skipped release and classify the failure: unspecified behaviour, a fixed bug, a newly run vet check, or changed tool output. Then fix the dependency, not the toolchain.

open as a page

Two CI runners build the same commit with different Go toolchains — how do you diagnose that drift?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Collect go version and go env GOTOOLCHAIN from every builder, then go version -m on each binary. The setting resolves from the environment, the go env -w file, then the built-in default, so identical installs still switch differently.

open as a page

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%

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.

open as a page

How does a `//go:build go1.21` line let one file use a newer language feature while the package still builds on older releases?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

A //go:build go1.21 constraint does two things: it excludes the file from builds by older Go releases, and it raises that one file's language version to 1.21 even when the module's go line is lower. A sibling file constrained with //go:build !go1.21 supplies the fallback.

open as a page

How does GOEXPERIMENT differ from GODEBUG when changing Go runtime or toolchain behaviour?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

GODEBUG is read at run time and restores an older, documented behaviour of a shipped feature. GOEXPERIMENT is applied when code is compiled and selects experimental toolchain and runtime features, so it needs a rebuild and carries no compatibility guarantee.

open as a page

In the Go standard library, what does a Deprecated: marker on a function change for callers?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

Nothing at build time. A Deprecated: paragraph is a documentation convention: the code still compiles, still runs and still gets security fixes. Under the Go 1 promise it is never removed, and the replacement ships alongside it.

open as a page

When is setting a GODEBUG opt-out in production the right call, and who owns removing it?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Prefer one named GODEBUG setting over rolling a Go upgrade back: it restores a single documented behaviour and keeps every other fix. Record it in the module, instrument it, and give removal a named owner and an upstream deadline.

open as a page

How would you set GOTOOLCHAIN policy across an org's laptops and build runners, and who owns bumping it?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Decide per environment what happens when a repository wants a compiler the machine lacks. A common split: auto on laptops, an exact GOTOOLCHAIN version pin in build images, and local where fetching an executable at build time is unacceptable.

open as a page