Lesson 19 · Concurrency & context
Data races & the memory model
Why synchronisation isn't optional — and how Go proves it.
Your win: define a data race precisely, find it with
-race, and state the happens-before guarantees that make our
mutex/channel code correct — not just tidy.
What a data race actually is
(Distinguish it from a race condition — a broader logic bug about timing. A data race is the specific memory-level version.)
The memory model: happens-before
The Go memory model defines when a read is guaranteed to see a write, via a happens-before ordering.1 The guarantees you rely on every day:
| Synchronisation | Happens-before guarantee |
|---|---|
| Channel send → receive | the send (and everything before it) happens-before the receive completes |
Mutex Unlock → next Lock | work before Unlock is visible after the next Lock |
WaitGroup Wait | all Done()-preceding work happens-before Wait returns |
sync.Once.Do | f's effects happen-before any Do returns |
So proper synchronisation doesn't just prevent corruption — it creates the ordering that makes one goroutine's writes visible to another.
Mutex
(kafka.go:590). That's not neatness: concurrent read+write
of a map is a data race, and for maps specifically Go throws a fatal
"concurrent map writes" that crashes the process — even without -race. The
Lock/Unlock establishes happens-before, so writes are ordered
and visible. Likewise the fan-in channel (Lesson 15): send happens-before receive, so
results transfer safely with no lock at all.
Find races with the detector
go test -race ./... // instrument & watch for unsynchronised access
go run -race main.go
The -race flag instruments memory accesses and reports races that
actually happen during the run, with both goroutines' stacks.2
It's the single best tool for concurrency bugs — run your tests with it in CI. (Caveat:
it only catches races that occur on that run, so it complements, not replaces, careful
design.)
x++ from two goroutines); concurrent
read+write of a plain map (fatal crash); and — historically — a captured
loop variable in a goroutine (fixed in Go 1.22, so not a race in our 1.25, but
still asked).
The Go Memory Model + the Data Race Detector
The memory model is the authoritative source on happens-before; the detector doc
shows exactly how to run and read -race.
Check yourself (from memory)
Q1. A data race requires concurrent access with…
Q2. You detect data races by running Go with…
go test -race / go run -race
instruments memory access and reports races that occur, with stacks.
Q3. The memory model guarantees a channel send…
-race). The mutex
creates a happens-before relationship (Unlock happens-before the next
Lock), so writes are ordered and visible. Verify with go test -race.select, the sync primitives,
context, the production patterns, and the memory model that makes them
correct. This is the largest slice of most Go interviews — and now the Kafka engine
reads like an open book. Part 5 is the last: testing, tooling &
performance.