Data-Race Hunt (Go Memory Model) (Go)
Topics: Go memory model, data races, sync.Mutex, sync/atomic
Problem
Counter and Registry compile and pass their single-threaded tests, but both contain data
races that surface under concurrent use. Find and fix them so the concurrent tests pass.
type Counter struct { ... }
func (c *Counter) Inc()
func (c *Counter) Value() int64
type Registry struct { ... }
func NewRegistry() *Registry
func (r *Registry) Set(key, val string)
func (r *Registry) Get(key string) (string, bool)
Counter — Inc does c.n++, a read-modify-write. Under 50 goroutines × 1000 increments the
concurrent test asserts the total is exactly 50000; an unsynchronized ++ loses updates and the
count comes up short. Fix with a sync.Mutex or sync/atomic.
Registry — Set/Get touch a plain map, which is not goroutine-safe. Concurrent access
makes the Go runtime abort with fatal error: concurrent map .... Fix with a sync.RWMutex (or
sync.Map).
How it's graded: the concurrent tests run on submit — a lost-update count or a map panic fails
them, so your fix has to genuinely synchronize, not just look right.
Key concepts
- Go memory model: without a happens-before relationship (established by a mutex, channel, or
sync/atomic), one goroutine's writes are not guaranteed to be visible to another — and the
compiler/CPU may reorder them. A data race is undefined behavior, not "just a stale read".
c.n++ is not atomic: it's load, add, store. Two goroutines can both load the same value and
both store +1, losing an increment. atomic.Int64.Add(1) makes it a single indivisible op.
- Maps and concurrency: the runtime actively detects concurrent map read/write and panics — it
doesn't need the
-race flag, and it can crash in production.
- Mutex vs atomic: atomics are faster for a single word (no lock, no scheduler involvement) but
don't compose — once you guard multiple fields together, you need a mutex. For a read-heavy
registry, a
sync.RWMutex lets concurrent readers proceed in parallel.
Profile it locally (optional): clone the repo and run
go test -race ./challenges/data-race/go/ to watch the race detector pinpoint each race with
file:line and the conflicting goroutines — the clearest way to see what the concurrent test is
failing on.
Sign in to submit your solution.