Cached at:
06/01/26, 02:32 PM
# Go Experiments Explained - Alex Edwards
Source: [https://www.alexedwards.net/blog/go-experiments-explained](https://www.alexedwards.net/blog/go-experiments-explained)
Go often ships with*experimental features*as part of a release\.
These experimental features can take different forms: sometimes they're completely new packages in the standard library, sometimes they're changes to the compiler or runtime, or – very occasionally – they can be breaking changes to Go's behavior\.
Most of the time, the purpose of experimental features is to get real\-world feedback from users before something graduates to*general availability*and becomes a permanent part of Go\. If the feature causes regressions, or gets negative feedback from the community, it can be changed before it is finalized – or even abandoned entirely\.
## Some examples
Let's look at a few recent examples to illustrate the type of things that Go experiments can cover\.
- Go 1\.24 shipped with experimental support for a new`testing/synctest`package \(which provides support for testing concurrent code\)\. After feedback, the package API was adjusted slightly and it graduated to general availability in Go 1\.25\.
- Go 1\.25 shipped with experimental support for a[new garbage collector](https://github.com/golang/go/issues/73581)design with better performance\. After incorporating feedback, the new garbage collector became the default in Go 1\.26\.
- Go 1\.21 shipped with an experimental[behavioral change to loop variable semantics](https://go.dev/wiki/LoopvarExperiment)\. This change closed off a previously common bug with Go code, but was technically a breaking change to the language\. Shipping the change as an experiment gave people a chance to test their code before the new behavior became the default in Go 1\.22\.
## Experiment lifecycle
There isn’t a single fixed lifecycle for experiments, but there are some common patterns\.
Most experiments initially ship as*off\-by\-default*\. You explicitly opt\-in to try out the feature, usually by setting the`GOEXPERIMENT`environment value \(which we'll talk about more in a moment\)\.
If things go well, one or two releases later the experimental feature is finalized, graduates to general availability, and becomes*on\-by\-default*\.
If an experiment affects the behavior of something, then after it graduates to general availability there is sometimes – but not always – a transitional grace period where it's possible to temporarily disable it and use the old behavior\. For example, in Go 1\.26 the new garbage collector design \(which we briefly mentioned above\) graduated to general availability and is on\-by\-default, but it's still possible to disable it and use the old garbage collector if you need to\.
So that's the most common pattern, but sometimes things take longer or work out differently\. For example:
- Go 1\.22 shipped with an experimental implementation of the compiler's inlining logic, which is still off\-by\-default and under evaluation more than two years later\.
- The same release also shipped with a*memory arenas*experiment\. After negative feedback and concerns from users, it remains off\-by\-default, is on[indefinite hold](https://avittig.medium.com/golangs-big-miss-on-memory-arenas-f1375524cc90), and may eventually be removed completely\.
Or finally, when the Go team is confident in a change, they might skip the feedback stage and go straight to general availability\.\.\. but there may still be a transitional grace period where it's possible to disable it\.
A good example of this is when Go 1\.24 changed its map implementation to use[Swiss tables](https://go.dev/blog/swisstable)\. The Go team was confident enough in the implementation and its performance benefits for this to go straight to general availability and become on\-by\-default, but – at least for now – it's still possible to opt out and use the old map implementation if you want to\.
So in practice there are really three broad experiment states:
- Off\-by\-default and under evaluation
- Off\-by\-default and on hold/dormant
- On\-by\-default with a temporary opt\-out
## Permanent experiments
Go also has a handful of experimental features that aren't really “experiments” in the normal sense\.
These are features that are off\-by\-default, but they're not under evaluation, not seeking feedback, and there's no expectation that they will ever graduate to general availability and become on\-by\-default\.
Although they are controlled by the`GOEXPERIMENT`environment setting in the same way as other experiments, really they are more like optional Go features that you might want to use in specialist situations\.
I'll refer to these as "permanent experiments" in the rest of this post\.
For example there is a[field tracking](https://codereview.appspot.com/6749064)diagnostic feature that tracks which struct fields are accessed\. It's been available for a decade, and there's[no intention](https://github.com/golang/go/issues/42712#issuecomment-737414957)for it to ever graduate to general availability\. Or there is a[static lock ranking](https://go.googlesource.com/go/+/0a820007e70fdd038950f28254c6269cd9588c02)feature, which is a diagnostic for finding potential deadlocks in the Go runtime\.
## What experiments are available right now?
It's surprisingly difficult to find out what experimental features are currently available and what their status is\.
Unfortunately, there isn't a page in the official Go documentation or[Go Wiki](https://go.dev/wiki/All)that tracks experiment status, and for this post I've had to piece together the information from various places\. If you want to do the same:
- You can get a list of all available experiments by running`$ go doc goexperiment\.Flags`\.
- You can figure out which experiments are on\-by\-default by reading the source code of`src/internal/buildcfg/exp\.go`– specifically looking at the`baseline`variable declaration in the`ParseGOEXPERIMENT\(\)`function\.
- You can cross\-reference the experiment names with the Go release notes and search through GitHub issues to try to figure out the current status\.
As far as I can tell, as of Go 1\.26 here are the available permanent experiments:
Experiment nameDescriptionStatus`FieldTrack`Diagnostic to track which struct fields are accessedOff\-by\-default and[permanent fixture](https://github.com/golang/go/issues/42712#issuecomment-737414957)`StaticLockRanking`Diagnostic to validate lock acquisition order to catch deadlocksOff\-by\-default and permanent fixture`CgoCheck2`Diagnostic to[check cgo pointer passing rules](https://tip.golang.org/doc/go1.21#runtimepkgruntime); too expensive to run by defaultOff\-by\-default and permanent fixture`BoringCrypto`Replaces Go's crypto with FIPS\-validated BoringSSL; no longer relevant since[Go 1\.24](https://go.dev/doc/go1.24#fips140)Off\-by\-default and[permanent fixture](https://github.com/golang/go/issues/42712#issuecomment-737414957)but[will be removed soon](https://go.dev/blog/fips140)`PreemptibleLoops`Allows scheduler to[preempt goroutines](https://github.com/golang/go/issues/10958)at loop back\-edges; generally not relevant since Go 1\.14, but still may be useful on[platforms where preemption is otherwise unsupported](https://go.dev/doc/go1.14#runtime)Off\-by\-default and permanent fixtureHere are the current off\-by\-default experiments and their status:
Experiment nameDescriptionStatus`HeapMinimum512KiB`Reduces minimum heap size from 4MB to 512KiB; may be useful for constrained environmentsOff\-by\-default and[likely dormant](https://github.com/golang/go/commit/c5c1955077cb94736b0f311b3a02419d166f45ac)`Arenas`[Memory arena](https://uptrace.dev/blog/golang-memory-arena)implementationOff\-by\-default and[on hold](https://github.com/golang/go/issues/51317)following negative feedback`NewInliner`Rewritten compiler inliner with better call\-site heuristicsOff\-by\-default and under evaluation \(available since[Go 1\.22](https://go.dev/doc/go1.22#compiler)\)`JSONv2`New`encoding/json/v2`package with improved JSON encoding/decoding functionsOff\-by\-default and under evaluation \(available since[Go 1\.25](https://go.dev/doc/go1.25#json_v2)\)`RuntimeSecret`New`runtime/secret`package with functions for zeroing out memory; available on Linux amd64/arm64 onlyOff\-by\-default and under evaluation \(available since[Go 1\.26](https://go.dev/doc/go1.26#new-experimental-runtimesecret-package)\)`GoroutineLeakProfile`Adds a`goroutineleak`pprof profile typeOff\-by\-default and under evaluation \(available since[Go 1\.26](https://go.dev/doc/go1.26#goroutineleak-profiles)\)`SIMD`New`simd/archsimd`package providing access to architecture\-specific SIMD operations; only available on amd64Off\-by\-default and under evaluation \(available since[Go 1\.26](https://go.dev/doc/go1.26#simd)\)`RuntimeFreegc`Allows immediate reuse of memory without waiting for a GC cycle when safe to do soOff\-by\-default and under evaluation \(available since Go 1\.26, but see[\#74299](https://github.com/golang/go/issues/74299)for status information\)`SizeSpecializedMalloc`Enables malloc implementations that are specialized per size classOff\-by\-default and under evaluation \(available since Go 1\.26, but see[\#74299](https://github.com/golang/go/issues/74299)for status information\)And here are the currently on\-by\-default experiments:
Experiment nameDescriptionStatus`LoopVar`Per\-iteration[loop variable scoping](https://go.dev/wiki/LoopvarExperiment)On\-by\-default since[Go 1\.22](https://go.dev/doc/go1.22), but opt\-out kept for edge cases`Dwarf5`DWARF 5 debug info generation; reduces binary sizeOn\-by\-default with a temporary opt\-out \(opt\-out[may be removed in a future release](https://go.dev/doc/go1.25#dwarf5-support)\)`RandomizedHeapBase64`Randomizes the heap base address at startup as a security measureOn\-by\-default with a temporary opt\-out \(opt\-out[expected to be removed in a future release](https://go.dev/doc/go1.26#heap-base-address-randomization)\)`GreenTeaGC`New garbage collector with improved performance; unavailable on darwin/ios/aixOn\-by\-default with a temporary opt\-out \(opt\-out[expected to be removed in Go 1\.27](https://go.dev/doc/go1.26#new-garbage-collector)\)`RegabiWrappers`ABI wrappers for calling between ABI0 and ABIInternal functions; only available on 64\-bit architecturesOn\-by\-default with a temporary opt\-out, but opt\-out is effective for s390x only, and[will be removed in Go 1\.27](https://github.com/golang/go/commit/6da07b9b44d2ae08921cb97900f076c96a7bf6fc)`RegabiArgs`Enables register arguments/results in all compiled Go functions; only available on 64\-bit architecturesOn\-by\-default with a temporary opt\-out, but opt\-out is effective for s390x only, and[will be removed in Go 1\.27](https://github.com/golang/go/commit/6da07b9b44d2ae08921cb97900f076c96a7bf6fc)## How do you enable and disable experiments?
Experiments are controlled using the`GOEXPERIMENT`environment setting\.
If there are some off\-by\-default experiments you want to try, you should include the experiment names as comma\-separated*lowercase*values in`GOEXPERIMENT`\. For example, if you wanted to build your application with the`JSONv2`and`GoroutineLeakProfile`experiments enabled, you would do so like this:
`$ GOEXPERIMENT=jsonv2,goroutineleakprofile go build \./\.\.\.`If there is an on\-by\-default experiment that you want to turn off, you do so by prefixing the lowercase experiment name with`no`\. For example, if you want to build your application with the`GreenTeaGC`and`RandomizedHeapBase64`experiments turned off, you would do so like this:
`$ GOEXPERIMENT=nogreenteagc,norandomizedheapbase64 go build \./\.\.\.`It's totally fine to mix enabled and disabled experiments:
`$ GOEXPERIMENT=jsonv2,nogreenteagc go build \./\.\.\.`Note that if you build the same package with different`GOEXPERIMENT`values, Go treats them as different builds and stores separate entries in the build cache\.
I've used`go build`in the examples above, but you can use exactly the same pattern when using`go run`or`go test`too\. If you want to try it yourself, try creating the following program which uses the experimental[`encoding/json/v2`](https://pkg.go.dev/encoding/json/v2)package:
`package main import \( "encoding/json/v2" "fmt" \) type Person struct \{ Name string \`json:"name"\` Age int \`json:"age"\` City string \`json:"city"\` \} func main\(\) \{ p := Person\{Name: "Ada", Age: 36, City: "Vienna"\} data, \_ := json\.Marshal\(p, json\.StringifyNumbers\(true\)\) fmt\.Println\(string\(data\)\) \}`If you run this normally, the program won't compile and you'll get an error message similar to this:
`$ go run main\.go package command\-line\-arguments imports encoding/json/v2: build constraints exclude all Go files in /usr/local/go/src/encoding/json/v2`But if you enable the`JSONv2`experiment, the program will run as expected:
`$ GOEXPERIMENT=jsonv2 go run main\.go \{"name":"Ada","age":"36","city":"Vienna"\}`## Which experiments should you actually care about?
If you're a run\-of\-the\-mill Gopher like me, who mainly uses Go to write programs rather than working on Go itself, most of the available experiments probably won't be very relevant to you\.
The most interesting and relevant ones probably are:
- [`GreenTeaGC`](https://go.dev/blog/greenteagc)– If you're using Go 1\.26, you're already using this by default\. But if you notice any performance or behavior problems, it's worth being aware that you can still disable it \(and you should also file an issue\)\.
- [`Dwarf5`](https://go.dev/doc/go1.25#dwarf5-support)– Again, if you're using Go 1\.25 or later then you're already using this by default\. But if you run into any problems, it's useful to know that you can still disable it\.
- [`JSONv2`](https://go.dev/doc/go1.25#json_v2)– I don't recommend switching to this until it graduates to general availability, but if you write a lot of code that deals with JSON, it's worth experimenting with the new`encoding/json/v2`package, familiarizing yourself with what's coming, and giving feedback if you notice any problems\.
- [`GoroutineLeakProfile`](https://go.dev/doc/go1.26#goroutineleak-profiles)– This one is immediately useful and worth enabling if you suspect you have a goroutine leak and need to debug it\.
- [`RuntimeSecret`](https://go.dev/doc/go1.26#new-experimental-runtimesecret-package)– Worth experimenting with and giving feedback on if you write cryptographic code or need to handle sensitive data\.
- [`RuntimeFreegc`](https://github.com/golang/go/issues/74299)– If you have an application that leans heavily on the garbage collector, it may be worth benchmarking your code with this enabled to see if it improves performance, and giving feedback if you notice any issues\.
Finally, it's worth emphasizing that experimental features are not covered by the Go compatibility promise\. Their APIs, behavior, and performance characteristics may all change, so it's generally a good idea to avoid adopting too early and depending on experimental features before they are finalized\.
But experimental features often act as a preview to some of the biggest changes in Go\. If you know that an experiment is likely to affect you or your code once it eventually becomes generally available and on\-by\-default, it's a good idea to try it out, run benchmarks where appropriate, and give feedback if you find issues\.
If you want to keep track of what experiments are available and their status, the Go release notes have recently started doing a much better job of documenting experimental features and how to use them\. Between this blog post and browsing the release notes when there's a new Go release, you should have a decent idea of what's going on\.