# Data races

> Two goroutines, one variable, at least one write and no synchronization — and how the race detector finds it.

Concept · Go · https://levelcode.org/concepts/data-races

A **data race** happens when two goroutines access the same memory at the same time, at least one of them writes, and nothing orders the two accesses. The result isn't just a stale value. Under Go's memory model, a racy program can see torn writes or corrupted maps, or behave in ways that make no sense.

```go
var hits int
var wg sync.WaitGroup
for range 1000 {
	wg.Go(func() { hits++ }) // read, add, write: three steps, interleaved
}
wg.Wait()
fmt.Println(hits) // often less than 1000
```

Maps are worse. Concurrent writes to a built-in map crash the program with `fatal error: concurrent map writes`, and you can't recover from that.

## Fixes

Pick one way to order the accesses:

```go
// 1. A mutex around the shared state
var mu sync.Mutex
mu.Lock(); hits++; mu.Unlock()

// 2. An atomic for a single counter or flag
var hits atomic.Int64
hits.Add(1)

// 3. Don't share: give the state to one goroutine and send it messages
results := make(chan int)
```

Go's proverb is *"Don't communicate by sharing memory; share memory by communicating."* Channels are a good fit for handing work or ownership from one goroutine to another. A mutex is simpler for protecting a cache or a counter. Both are fine; the race is what's wrong.

## The race detector

```text
$ go test -race ./...
WARNING: DATA RACE
Write at 0x00c000014108 by goroutine 8:
  main.main.func1() main.go:12
Previous write at 0x00c000014108 by goroutine 7:
  main.main.func1() main.go:12
```

`-race` instruments every memory access and reports races that **actually happen during the run**. It has no false positives, but it misses code paths the run didn't reach. Run your tests with `-race` in CI, and use concurrent test cases (`t.Parallel()` and handlers hit from several goroutines) so the racy paths actually run.

## Where races hide in servers

- **Every HTTP request runs in its own goroutine.** Any package-level variable, or any field on a shared handler struct that you write to, is shared state.
- **A closure captures variables, not values.** Since Go 1.22 each loop iteration gets a fresh variable, but a variable declared outside the loop and captured by a goroutine is still shared.
- **Lazy initialization** ("if cache == nil, build it") needs `sync.Once`.

JavaScript avoids this whole class of bug by running your code on one thread. See [the event loop](https://levelcode.org/concepts/event-loop) for what it gives up instead.
