go-concurrencylisted
Install: claude install-skill CasLubbers/code-design-skills
# Go concurrency
Concurrency in Go is cheap to start and expensive to get wrong. The failures — leaks, races, deadlocks — are invisible in the happy path and appear under load.
## Never start a goroutine without knowing how it stops
Before writing `go f()`, answer: what makes this return, and who waits for it? A goroutine blocked forever on a channel send or receive is a permanent leak of its stack and everything it references.
```go
// Bad — if nobody ever receives, this goroutine never exits
go func() { ch <- compute() }()
// Good — cancellation ends it, and the caller waits
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
select {
case ch <- compute():
case <-ctx.Done():
}
}()
wg.Wait()
```
The `Add` goes before the `go`, never inside the goroutine — otherwise `Wait` can run first and return immediately.
## Context carries cancellation, not data
Pass `ctx` as the first parameter. Never store it in a struct. Every blocking operation selects on `ctx.Done()`, and every function that takes a `ctx` must honour it.
```go
func poll(ctx context.Context, interval time.Duration) error {
t := time.NewTicker(interval)
defer t.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-t.C:
if err := checkOnce(ctx); err != nil { return err }
}
}
}
```
Always call the `cancel` returned by `WithCancel` / `WithTimeout`, usually via `defer` — skipping it leaks the timer and