Go

How does Go ship behavior changes without breaking old code? Explain GODEBUG, the godebug directive and //go:debug. What does this print?

Question 439HardGo 1.22 to 1.25
// go.mod:  go 1.20   (built with a Go 1.24 toolchain)
package main

import "fmt"

func main() {
    defer func() { fmt.Printf("%T\n", recover()) }()
    panic(nil)
}

Answer: with go 1.20 it prints <nil>, because recover() returns nil and so looks like "no panic". Change the go line to go 1.21 or later and it prints *runtime.PanicNilError.

Since Go 1.21, behavior changes that could break programs are gated behind named GODEBUG settings (such as panicnil, asynctimerchan, httpmuxgo121, x509keypairleaf). The default for each setting comes from the main module's go line only. The go lines of dependencies do not matter. So upgrading the toolchain keeps the old behavior, and raising your go line opts you in.

// go.mod (Go 1.23+): keep old behavior while moving the go line forward
go 1.24.0
godebug (
    panicnil=1
    asynctimerchan=1
)

// or in a main package source file, before the package clause:
//go:debug panicnil=1
package main

// or at run time (highest priority):
//   GODEBUG=panicnil=1,http2client=0 ./app

go list -f '{{.DefaultGODEBUG}}' ./cmd/app   # what got baked in

Gotchas: //go:debug is honored only in the main package (and in test files for tests). Settings are kept for at least two years (four releases) and then may be removed. The runtime exposes /godebug/non-default-behavior/<name>:events metrics, so you can measure whether the old behavior is still being used before you drop the override. Interviewers want "defaults follow the main module's go line, which is why toolchain upgrades are safe".

More on Modules, Packages & Tooling

All 36 Modules, Packages & Tooling questions