Race Detector: Running and Interpreting Output
Go's race detector instruments memory accesses at compile time to find unsynchronized concurrent reads and writes.
Search across all documentation pages
Go's race detector instruments memory accesses at compile time to find unsynchronized concurrent reads and writes.
Run it in CI on packages that use goroutines; learn to read its reports to fix real bugs, not silence noise.
Enable with -race on go test, go run, or go build.
A data race is when two goroutines access the same memory location, at least one is a write, and there is no happens-before ordering.
Reports show goroutine stacks for both sides of the race.
Fix with mutexes, channels, atomics, or restructuring ownership - never "it passed once without -race."
Quick-reference recipe card - copy-paste ready.
go test -race ./...
go run -race .
go build -race -o bin/app .
./bin/app # staging only - race builds are slower and use more memoryWhen to reach for this:
-count=10.Buggy code and typical detector output pattern:
package counter
import "sync"
var n int // BUG: unsynchronized
func Inc() { n++ }
func BrokenParallel(wg *sync.WaitGroup) {
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
Inc()
}()
}
}$ go test -race ./...
==================
WARNING: DATA RACE
Write at 0x... by goroutine 7:
example.com/counter.Inc()
example.com/counter.BrokenParallel.func1()
Previous read at 0x... by goroutine 6:
...
==================Fixed version:
var (
mu sync.Mutex
n int
)
func Inc() {
mu.Lock()
n++
mu.Unlock()
}What this demonstrates:
Inc and callers appear in stacks - start reading from your code frames.-race until clean.| Section | Meaning |
|---|---|
Write at / Read at | Access type and address |
by goroutine N | Stack of one participant |
Previous read/write | Other participant stack |
Goroutine N created at | Where the goroutine started |
| Pattern | Fix |
|---|---|
| Shared map without lock | Mutex or pass ownership via channel |
| Check-then-act | Lock entire check+act or use atomic CAS |
| Lazy init race | sync.Once |
| Append to shared slice | Mutex or single writer goroutine |
# Flaky race hunt
go test -race -count=50 ./pkg
# CI snippet
go test -race -short ./...-race job on main packages.-parallel 1 - Hides races. Fix: synchronize code under test.-race clean - Logic races remain. Fix: atomics for simple counters; mutex for invariants.go test -timeout, deadlock detectors in staging.| Alternative | Use When | Don't Use When |
|---|---|---|
-race tests | Default concurrency verification | Production binaries |
Mutex + code review | Prevent races by design | Substitute for ever running -race |
go test -fuzz | Input-driven bugs | Data races without fuzz concurrency |
| Thread sanitizers (other langs) | N/A in Go | Go code - use built-in race detector |
Yes - often several times slower.
Run on CI nightly or per-PR for affected packages if full suite is too slow.
Yes - races are timing-dependent.
-race forces interleaving instrumentation to surface them.
No for latency-sensitive production.
Use staging or dedicated soak environments with -race builds.
Reports still show stacks.
Serialize init with sync.Once or avoid mutable globals.
The handoff is synchronized, but mutating a struct after sending a pointer still races if other goroutines read it unsynchronized.
Proper atomic ops do not race on the same word.
Mixing atomics and mutex on overlapping data is still error-prone.
No official suppression like some C tools.
Fix the bug or refactor test design.
Supported on linux/amd64, darwin/amd64, darwin/arm64, windows/amd64, and other listed ports.
Check go.dev docs for current platform list.
A race needs two concurrent accesses.
Each stack is one goroutine's participation.
Linters catch some race-prone APIs but do not replace -race.
Use both in CI.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026