What does the GODEBUG environment variable control, and how is its value formatted?
answer
- an environment variable, not a compiler flag
- comma-separated pairs
- each name restores one older behaviour
- read when the process starts
- panicnil=1 is the classic example
basics
~20 sGODEBUG 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.
solid answer
~40 sGODEBUG is a comma-separated list of `name=value` settings read by the Go runtime and parts of the standard library when a program starts: `GODEBUG=panicnil=1,asynctimerchan=1 ./myserver`. Each name is a documented switch that restores the behaviour of an earlier Go release for exactly one change — `panicnil=1` brings back the pre-Go-1.21 rule where `panic(nil)` panicked with a nil value instead of a `*runtime.PanicNilError`. It is not a debug-logging switch and it is not a general feature-flag system: the set of names is fixed by the Go release you built with, and each one is documented individually. Because it is an environment variable, you can flip it on a binary you already have, which makes it the fastest way to test whether a behaviour change in a new Go release is what broke you.
code
text · 5 lines# restore the pre-Go-1.21 panic(nil) behaviour for one process
GODEBUG=panicnil=1 ./billing-worker
# two settings at once, no spaces
GODEBUG=panicnil=1,asynctimerchan=1 ./billing-workergo deeper
Recall the shape: GODEBUG=name=value, comma-separated, read at process start, one named setting per behaviour change. Have one concrete example ready, such as panicnil=1.
Explain why the mechanism exists at all: Go ships a deliberate behaviour change and gives you a named, documented switch back to the old semantics without touching your source or your build.
Show you would use it as a diagnosis tool during an upgrade — flip one setting on one instance to confirm which change broke you — and that you treat it as temporary, not a fix.
Be ready to say who is allowed to set one in production, where it should be recorded so it is visible in review, and how the organisation gets rid of it before the release that deletes it.
## The problem GODEBUG solves Go promises that code which compiles and runs today keeps working on later toolchains. Sometimes, though, a change is worth making anyway — a bug fix, a security tightening, a semantic correction — and it will change what some existing program does at run time. GODEBUG is the escape hatch for exactly those changes: for each one, the Go team ships a named setting that restores the old behaviour, so that a team hit by the change can keep running while they fix their code. The name is misleading. Despite "DEBUG", it is not a verbosity knob and it does not turn on logging in general. It is a list of compatibility switches (plus a separate family of diagnostic knobs that belong to the performance-tooling discussion, not here). ## The format The value is a comma-separated list of `name=value` pairs, with no spaces: ``` GODEBUG=panicnil=1 ./billing-worker GODEBUG=panicnil=1,asynctimerchan=1 ./billing-worker ``` Most compatibility settings are boolean-ish: `1` means "behave the old way", `0` means "behave the new way". Which value is the default depends on the release and on how the program was built. The settings are consulted when the process starts, by the runtime and by the standard-library packages that own the affected behaviour. That is the property that makes GODEBUG operationally useful: you can take a binary that is already built, already signed, already deployed, and change one behaviour by changing the environment of the next process you start. Nothing is recompiled and nothing in your source tree changes. A name the running toolchain does not know is not an error you will notice — it simply has no effect. That matters later, because compatibility settings are eventually removed. ## A concrete example Before Go 1.21, `panic(nil)` panicked with a nil value, so `recover()` returned nil and a `if r := recover(); r != nil` guard treated the panic as if nothing had happened — a real source of silently swallowed failures. Go 1.21 changed `panic(nil)` to panic with a `*runtime.PanicNilError` instead, so `recover()` returns something non-nil. That is a better rule, and it is also a behaviour change: a program that relied on the old shape can be made to behave the old way again with `GODEBUG=panicnil=1`. Another: Go 1.23 changed the channels returned by `time.Timer` and `time.Ticker` so they are unbuffered and the timers can be collected earlier. Code that had been written around the old buffered channel could set `GODEBUG=asynctimerchan=1` to get the old behaviour back — until Go 1.27 removed that setting along with the old implementation. ## What GODEBUG is not - It is **not** a place to put your own application feature flags. The names are defined by the Go release; you cannot add one. - It is **not** a compiler switch. It changes what the already-compiled runtime and standard library do, not how your code is generated. The build-time equivalent, for experimental toolchain features, is `GOEXPERIMENT`, and that one does require a rebuild. - It is **not** permanent. Each compatibility setting exists for a documented window and is then deleted, so a program leaning on one is on a clock. - It is **not** the only way to set these values. A module can bake defaults into the build so they apply without anyone setting an environment variable, and the environment variable then overrides those baked-in defaults at run time. ## How you actually meet it The usual first encounter is an upgrade. You move a service from one Go release to the next, something behaves differently, and the release notes for that change name the GODEBUG setting that turns it off. Setting it on one instance is a cheap, reversible experiment that answers "is this change what broke us?" in one deploy — far cheaper than bisecting the whole upgrade. Once the answer is yes, the setting buys time; it does not substitute for fixing the code.
- Does changing GODEBUG require rebuilding the binary?No. The settings are read by the runtime and standard library when the process starts, so you can flip one on a binary that is already built and deployed by changing the environment of the next process. That is what makes it a viable incident-time action. Rebuilding is only needed if you want the setting baked into the module so it also applies in tests and to anyone else who builds it.
- Can you invent your own GODEBUG name for your application's feature flags?No. The set of names is defined by the Go release — each one corresponds to a specific documented behaviour change in the runtime or standard library. A name the toolchain does not recognise simply has no effect, so an invented setting fails silently. Application feature flags belong in your own configuration, not in GODEBUG.
- What does panicnil=1 actually change?It restores the behaviour from before Go 1.21, where `panic(nil)` panicked with a nil value so `recover()` returned nil and the usual `r != nil` guard skipped the recovery path. From Go 1.21 the runtime panics with a `*runtime.PanicNilError` instead, so `recover()` returns a non-nil error value and the panic stops being invisible.
saying these in an interview costs you the question
- Thinks GODEBUG turns on verbose runtime logging
- Believes it is a general-purpose application feature-flag system
- Says the binary must be recompiled for a GODEBUG change
- Writes settings space-separated or one per variable
- Assumes a GODEBUG setting is supported forever