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/synctestpackage (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 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. 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, 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. 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 diagnostic feature that tracks which struct fields are accessed. It's been available for a decade, and there's no intention for it to ever graduate to general availability. Or there is a static lock ranking 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 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 thebaselinevariable declaration in theParseGOEXPERIMENT()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 name | Description | Status |
|---|---|---|
FieldTrack |
Diagnostic to track which struct fields are accessed | Off-by-default and permanent fixture |
StaticLockRanking |
Diagnostic to validate lock acquisition order to catch deadlocks | Off-by-default and permanent fixture |
CgoCheck2 |
Diagnostic to check cgo pointer passing rules; too expensive to run by default | Off-by-default and permanent fixture |
BoringCrypto |
Replaces Go's crypto with FIPS-validated BoringSSL; no longer relevant since Go 1.24 | Off-by-default and permanent fixture but will be removed soon |
PreemptibleLoops |
Allows scheduler to preempt goroutines at loop back-edges; generally not relevant since Go 1.14, but still may be useful on platforms where preemption is otherwise unsupported | Off-by-default and permanent fixture |
Here are the current off-by-default experiments and their status:
| Experiment name | Description | Status |
|---|---|---|
HeapMinimum512KiB |
Reduces minimum heap size from 4MB to 512KiB; may be useful for constrained environments | Off-by-default and likely dormant |
Arenas |
Memory arena implementation | Off-by-default and on hold following negative feedback |
NewInliner |
Rewritten compiler inliner with better call-site heuristics | Off-by-default and under evaluation (available since Go 1.22) |
JSONv2 |
New encoding/json/v2 package with improved JSON encoding/decoding functions |
Off-by-default and under evaluation (available since Go 1.25) |
RuntimeSecret |
New runtime/secret package with functions for zeroing out memory; available on Linux amd64/arm64 only |
Off-by-default and under evaluation (available since Go 1.26) |
GoroutineLeakProfile |
Adds a goroutineleak pprof profile type |
Off-by-default and under evaluation (available since Go 1.26) |
SIMD |
New simd/archsimd package providing access to architecture-specific SIMD operations; only available on amd64 |
Off-by-default and under evaluation (available since Go 1.26) |
RuntimeFreegc |
Allows immediate reuse of memory without waiting for a GC cycle when safe to do so | Off-by-default and under evaluation (available since Go 1.26, but see #74299 for status information) |
SizeSpecializedMalloc |
Enables malloc implementations that are specialized per size class | Off-by-default and under evaluation (available since Go 1.26, but see #74299 for status information) |
And here are the currently on-by-default experiments:
| Experiment name | Description | Status |
|---|---|---|
LoopVar |
Per-iteration loop variable scoping | On-by-default since Go 1.22, but opt-out kept for edge cases |
Dwarf5 |
DWARF 5 debug info generation; reduces binary size | On-by-default with a temporary opt-out (opt-out may be removed in a future release) |
RandomizedHeapBase64 |
Randomizes the heap base address at startup as a security measure | On-by-default with a temporary opt-out (opt-out expected to be removed in a future release) |
GreenTeaGC |
New garbage collector with improved performance; unavailable on darwin/ios/aix | On-by-default with a temporary opt-out (opt-out expected to be removed in Go 1.27) |
RegabiWrappers |
ABI wrappers for calling between ABI0 and ABIInternal functions; only available on 64-bit architectures | On-by-default with a temporary opt-out, but opt-out is effective for s390x only, and will be removed in Go 1.27 |
RegabiArgs |
Enables register arguments/results in all compiled Go functions; only available on 64-bit architectures | On-by-default with a temporary opt-out, but opt-out is effective for s390x only, and will be removed in Go 1.27 |
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 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– 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– 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– 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 newencoding/json/v2package, familiarizing yourself with what's coming, and giving feedback if you notice any problems.GoroutineLeakProfile– This one is immediately useful and worth enabling if you suspect you have a goroutine leak and need to debug it.RuntimeSecret– Worth experimenting with and giving feedback on if you write cryptographic code or need to handle sensitive data.RuntimeFreegc– 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.