Go has fuzzing in the standard toolchain from Go 1.18. You write a function named FuzzSomething that takes a testing.F, add a few seed values, and run go test with the fuzz flag. When it finds a failure, Go saves the input in your repo so it becomes a regression test.
The shape of a fuzz test
From the Go documentation: a fuzz test is a function named like FuzzXxx, accepting only a testing.F, with no return value, in a file ending in _test.go. Inside it you call Fuzz once, with a function whose first parameter is a testing.T followed by the values to fuzz.
func FuzzReverse(f *testing.F) {
f.Add("hello")
f.Fuzz(func(t *testing.T, s string) {
r := Reverse(s)
if Reverse(r) != s {
t.Errorf("round trip failed for %q", s)
}
})
}
Seed values added with f.Add must match the argument types in order. Supported types include string, byte slices, the integer types, floats and bool.
Test properties, not just crashes
This example does not wait for a panic. It checks a property: reversing twice gives back the original. Go reports a failure for panics, calls to t.Fail, t.Error or t.Fatal, unrecoverable errors, and an execution that takes too long. The docs state the timeout for one execution of a fuzz target is currently 1 second.
Round trips, comparing two implementations, and "output is valid" checks all turn a fuzzer into a logic bug finder, not only a crash finder.
Run it
Plain go test runs each seed as an ordinary unit test. To fuzz, name the test:
go test -fuzz=FuzzReverse -fuzztime=30s
The flag takes a regex that matches a single fuzz test. Without -fuzztime, fuzzing can run indefinitely if no error is found. The output shows elapsed time, executions, and a count of new interesting inputs, which are the ones that reached new coverage.
What happens on failure
The engine minimizes the failing input and writes it to testdata/fuzz/FuzzReverse/ under a hash name. Rerun just that case with go test -run=FuzzReverse/<hash>. Once the bug is fixed, the file stays in place, and per the docs it is run by default with go test as a regression test. Commit it.
Corpus files begin with a version line, go test fuzz v1, followed by one line per argument value. Seed files you place in testdata/fuzz/FuzzName are also run by default.
Tips from the docs
- Keep the target fast and deterministic.
- Do not depend on global state, because the target runs in parallel in nondeterministic order.
- Expect coverage guidance to work best on AMD64 and ARM64.
- Use
-parallelto set the number of fuzzing processes, which defaults to GOMAXPROCS.
Where it fits in CI
Plain go test in CI replays seeds and saved failures, which is cheap and catches regressions. A longer fuzz run, say nightly, hunts for new bugs. Commit anything it finds.
Quick check
Does go test run my fuzz test forever?
No. Without the fuzz flag it only runs the seed corpus as unit tests. Fuzzing starts only when you pass -fuzz.
Where does Go store the generated corpus?
Generated entries live in the build cache under a fuzz directory, while seed files and failing inputs live in testdata/fuzz in your repository.
Sources
Last reviewed: 19 October 2026.