How does GOEXPERIMENT differ from GODEBUG when changing Go runtime or toolchain behaviour?
answer
- one is read at build, one at start
- rebuild versus restart
- shipped change versus unfinished feature
- experiments graduate or vanish
- go env shows the build-time one
basics
~20 sGODEBUG 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.
solid answer
~50 sThey sit on opposite sides of the build. `GODEBUG` names a compatibility switch that the runtime and standard library consult when a process starts, so flipping it changes an existing binary's behaviour with no rebuild, and every name is documented with a support window. `GOEXPERIMENT` is read by the go command while compiling: it selects experimental features in the toolchain and the runtime that gets linked into your binary, which means it changes the build and you must rebuild to change it — `go env GOEXPERIMENT` shows the current value. Experiments come and go without a compatibility promise: `GOEXPERIMENT=synctest` gated `testing/synctest` in Go 1.24 before it graduated in 1.25, and Green Tea garbage collection went from `GOEXPERIMENT=greenteagc` to on-by-default in Go 1.26 with `nogreenteagc` as the way out. So GODEBUG is for deferring a change you already have; GOEXPERIMENT is for opting into or out of one that is still being decided.
go deeper
Remember the headline: one takes effect when the program runs, the other when it is compiled, so only the first can be changed on a binary you already have.
Explain the lifecycle difference — a documented switch back from a shipped change versus an opt-in to something unfinished — and name a feature that moved from experiment to default.
Show you would set the build-time variable in the build definition rather than inherit it from a shell, and that you can say why an experiment is a thinner branch to stand on during an upgrade.
Own the policy on experiments: whether the organisation ships them at all, what pinning and exit plan is required if it does, and who reviews that decision at each toolchain upgrade.
## Two knobs, two moments Both are environment variables spelled in capitals and both change how a Go program behaves, which is why they get confused. The distinction that resolves every follow-up question is **when the variable is read**. `GODEBUG` is read by the running program. The runtime and the standard-library packages that own the affected behaviour consult it at process start. The binary is unchanged; you are choosing between two code paths that are both already compiled into it. `GOEXPERIMENT` is read by the go command while it builds. It selects experimental features in the compiler, the linker and the runtime, and the result is baked into the artefact. Two builds with different values are genuinely different binaries, they cache separately, and you cannot change the choice by editing a deployment manifest. ## What each one is for **GODEBUG covers a shipped, sanctioned behaviour change.** Go changed something on purpose, knows it may break someone, and provides a named way back to the old behaviour for a documented period. The population of names is fixed by the release you build with, each is written up individually, and the whole point is that the *new* behaviour is the one Go intends you to end on. Examples in this family: `panicnil=1` restoring the pre-Go-1.21 handling of `panic(nil)`, and `asynctimerchan=1` restoring the buffered timer channels that Go 1.23 replaced. **GOEXPERIMENT covers a feature that is not settled yet.** It is how the Go team ships something to willing users before committing to it. `testing/synctest` was available under `GOEXPERIMENT=synctest` in Go 1.24 and became generally available in Go 1.25. The `encoding/json/v2` work rode `GOEXPERIMENT=jsonv2` before shipping in Go 1.27, which then offered `nojsonv2` for anyone who needed the old package semantics. The Green Tea garbage collector was `GOEXPERIMENT=greenteagc` in Go 1.25, became the default in Go 1.26, and left `nogreenteagc` behind as the opt-out. A goroutine-leak profile was similarly available as `GOEXPERIMENT=goroutineleakprofile` before shipping. Notice the shape of that lifecycle: an experiment either graduates — at which point the flag stops being interesting and, if the change is user-visible enough, an opt-out flag appears — or it is deleted. Either way, code that depends on an experiment is standing on something with no compatibility promise at all. ## Consequences that come up in practice - **Rebuild versus restart.** A GODEBUG change is a restart. A GOEXPERIMENT change is a rebuild and a redeploy, so it is not an incident-time lever. - **Reproducibility.** Because GOEXPERIMENT participates in the build, a machine with a different value in its environment produces a different binary from the same source. That is a reason to set it explicitly in the build definition rather than let a developer's shell decide. - **Support.** A GODEBUG compatibility setting is documented and kept for a stated window before removal. An experiment can disappear in the next release with nothing but a release note. - **Scope.** GODEBUG changes behaviour of standard-library and runtime code paths. GOEXPERIMENT can change code generation and the runtime implementation itself — a different garbage-collection algorithm is not something a run-time flag could switch. ## The one-sentence test If the question is "Go changed something and I need the old behaviour for a while", the answer is a GODEBUG setting. If the question is "Go has something new that is not finished and I want in (or want out)", the answer is GOEXPERIMENT and a rebuild. Reaching for the wrong one shows up immediately: people try to set an experiment on a running deployment and are puzzled that nothing happens, because the binary in front of them was compiled without it.
- Why can't a garbage-collection algorithm be selected with a GODEBUG setting the way panicnil is?Because the collector is part of the runtime that gets compiled and linked into the binary, and swapping the mark algorithm is a build-time choice, not a branch the shipped runtime carries. That is precisely the kind of change GOEXPERIMENT exists for: Green Tea rode GOEXPERIMENT=greenteagc before becoming the default in Go 1.26, with nogreenteagc as the opt-out.
- What happens to a build if two machines have different GOEXPERIMENT values?They produce different binaries from identical source, and the results cache separately. Anything relying on reproducible builds has to set the value explicitly in the build definition instead of inheriting whatever is in a developer's or a runner's shell. GODEBUG has no such effect on the build; it only changes what a process does when it runs.
- Is depending on a GOEXPERIMENT feature in production reasonable?Only with eyes open. An experiment carries no compatibility promise: it can change shape or be removed in the next release with a release note and nothing else. If you take one, pin the toolchain version, keep the dependency narrow enough to remove in a single change, and re-evaluate at every upgrade rather than at the point it breaks.
saying these in an interview costs you the question
- Says both are read at process start
- Tries to enable an experiment on an already-built binary
- Thinks experiments carry the same support window as compatibility settings
- Claims GODEBUG can change code generation
- Treats a graduated experiment's flag as permanent