Skip to main content
skill CX Codex

golang-concurrency-patterns — Claude Code skill: Go concurrency

A skill that feeds your agent production Go concurrency patterns: context cancellation, errgroup, worker pools, and the usual race and deadlock traps.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add bobmatnyc/claude-mpm-skills --skill golang-concurrency-patterns -a claude-code
Open on GitHub

What golang-concurrency-patterns does

This skill is a reference for concurrent Go in production services. It ships working examples: goroutine lifetimes stopped through context, fan-out and fan-in with errgroup.WithContext, bounded parallelism via a semaphore, a worker pool with a fixed worker count, pipeline stages, and periodic work on time.NewTicker instead of time.After inside a loop.

There are decision trees too. Channel or sync.RWMutex or atomic for shared state. errgroup against sync.WaitGroup. Buffered channel against unbuffered. Each pattern comes with a wrong version next to it — a goroutine with no stop condition, a channel closed by the receiver, a WaitGroup that swallows the first error.

The skill is not user-invocable and does not invoke the model on its own, so it loads as background material while the agent writes concurrent code. Run your tests with -race and timeouts anyway. The examples show correct shapes, but they will not catch a race in your own logic.

What it helps with

  • write fan-out that cancels sibling tasks on the first error
  • cap the number of goroutines so a service does not overload
  • build a worker pool with predictable resource use
  • track down a goroutine that leaks after a request finishes
  • pick between channel, mutex and atomic for shared state

Example prompts for the agent

  • rewrite this job loop with errgroup.WithContext and cancel on error
  • bound the parallelism in this fan-out to 8 concurrent goroutines
  • check my worker pool for goroutine leaks and deadlocks

How to install golang-concurrency-patterns

1Skills CLI (Vercel)

npx skills add bobmatnyc/claude-mpm-skills --skill golang-concurrency-patterns -a claude-code

2Manually: put the files in place

~/.claude/skills/golang-concurrency-patterns/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/golang-concurrency-patterns/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about golang-concurrency-patterns

How do I install golang-concurrency-patterns in Claude?

Run npx skills add bobmatnyc/claude-mpm-skills --skill golang-concurrency-patterns -a claude-code. The skill file then lives at ~/.claude/skills/golang-concurrency-patterns/SKILL.md.

What does golang-concurrency-patterns do?

It gives the agent Go concurrency patterns: cancellation with context, errgroup, semaphores, worker pools, pipelines, plus the common race and deadlock mistakes.

Does the skill run as a command?

No. It sets user-invocable: false and disable-model-invocation: true, so it acts as reference material rather than a slash command.

Source text

SKILL.md · MIT
---
name: golang-concurrency-patterns
description: "Go concurrency patterns for production services: context cancellation, errgroup, worker pools, bounded parallelism, fan-in/fan-out, and common race/deadlock pitfalls"
user-invocable: false
disable-model-invocation: true
version: 1.0.0
category: toolchain
author: Claude MPM Team
license: MIT
progressive_disclosure:
  entry_point:
    summary: "Write race-free, cancellation-aware Go using context, errgroup, channels, and bounded concurrency patterns"
    when_to_use: "When implementing concurrent pipelines, background workers, fan-out/fan-in, or fixing goroutine leaks, deadlocks, and data races in Go services"
    quick_start: "1. Thread context everywhere 2. Use errgroup.WithContext for fan-out 3. Bound concurrency with a semaphore 4. Make goroutine lifetimes explicit 5. Test with -race and timeouts"
  token_estimate:
    entry: 150
    full: 5000
context_limit: 800
tags:
  - golang
  - concurrency
  - context
  - errgroup
  - channels
  - mutex
  - race-detector
  - worker-pool
requires_tools: []
---

# Go Concurrency Patterns (Production)

## Overview

Go concurrency scales when goroutine lifetimes are explicit, cancellation is propagated with `context.Context`, and shared state is protected (channels or locks). Apply these patterns to build reliable services and avoid common failure modes: goroutine leaks, deadlocks, and data races.

## Quick Start

**Default building blocks**
- Use `context` to drive cancellation and deadlines.
- Use `errgroup.WithContext` for fan-out/fan-in with early abort.
- Bound concurrency (avoid unbounded goroutines) with a semaphore or worker pool.
- Prefer immutable data; otherwise protect shared state with a mutex or make a single goroutine the owner.

**Avoid**
- Fire-and-forget goroutines in request handlers.
- `time.After` inside hot loops.
- Closing channels from the receiver side.
- Sharing mutable variables across goroutines without synchronization.

## Core Concepts

### Goroutine lifecycle

Treat goroutines as resources with a clear owner and shutdown condition.

✅ **Correct: stop goroutines via context**
```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
    ticker := time.NewTicker(250 * time.Millisecond)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            // do work
        }
    }
}()
```

❌ **Wrong: goroutine without a stop condition**
```go
go func() {
    for {
        doWork() // leaks forever
    }
}()
```

### Channels vs mutexes (choose intentionally)

- Use **channels** to model ownership/serialization of state or to pipeline work.
- Use **mutexes** to protect shared in-memory state with simple read/write patterns.

✅ **Correct: one goroutine owns the map**
```go
type req struct {
    key   string
    reply chan<- int
}

func mapOwner(ctx context.Context, in <-chan req) {
    m := map[string]int{}
    for {
        select {
        case <-ctx.Done():
            return
        case r := <-in:
            r.reply <- m[r.key]
        }
    }
}
```

✅ **Correct: mutex protects shared map**
```go
type SafeMap struct {
    mu sync.RWMutex
    m  map[string]int
}

func (s *SafeMap) Get(k string) (int, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    v, ok := s.m[k]
    return v, ok
}
```

## Patterns

### 1) Fan-out/fan-in with cancellation (`errgroup`)

Use `errgroup.WithContext` to run concurrent tasks, cancel siblings on error, and wait for completion.

✅ **Correct: cancel on first error**
```go
g, ctx := errgroup.WithContext(ctx)

for _, id := range ids {
    id := id // capture
    g.Go(func() error {
        return process(ctx, id)
    })
}

if err := g.Wait(); err != nil {
    return err
}
```

❌ **Wrong: WaitGroup loses the first error and does not propagate cancellation**
```go
var wg sync.WaitGroup
for _, id := range ids {
    wg.Add(1)
    go func() {
        defer wg.Done()
        _ = process(context.Background(), id) // ignores caller ctx + captures id
    }()
}
wg.Wait()
```

### 2) Bounded concurrency (semaphore pattern)

Bound parallelism to prevent CPU/memory exhaustion and downstream overload.

✅ **Correct: bounded fan-out**
```go
limit := make(chan struct{}, 8) // max 8 concurrent
g, ctx := errgroup.WithContext(ctx)

for _, id := range ids {
    id := id
    g.Go(func() error {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case limit <- struct{}{}:
        }
        defer func() { <-limit }()

        return process(ctx, id)
    })
}

return g.Wait()
```

### 3) Worker pool (durable throughput)

Use a fixed number of workers for stable throughput and predictable resource usage.

✅ **Correct: worker pool with context stop**
```go
type Job struct{ ID string }

func runPool(ctx context.Context, jobs <-chan Job, workers int) error {
    g, ctx := errgroup.WithContext(ctx)

    for i := 0; i < workers; i++ {
        g.Go(func() error {
            for {
                select {
                case <-ctx.Done():
                    return ctx.Err()
                case j, ok := <-jobs:
                    if !ok {
                        return nil
                    }
                    if err := handleJob(ctx, j); err != nil {
                        return err
                    }
                }
            }
        })
    }

    return g.Wait()
}
```

### 4) Pipeline stages (fan-out between stages)

Prefer one-directional channels and close only from the sending side.

✅ **Correct: sender closes**
```go
func stageA(ctx context.Context, out chan<- int) {
    defer close(out)
    for i := 0; i < 10; i++ {
        select {
        case <-ctx.Done():
            return
        case out <- i:
        }
    }
}
```

❌ **Wrong: receiver closes**
```go
func stageB(in <-chan int) {
    close(in) // compile error in<-chan; also wrong ownership model
}
```

### 5) Periodic work without leaks (`time.Ticker` vs `time.After`)

Use `time.NewTicker` for loops; avoid `time.After` allocations in hot paths.

✅ **Correct: ticker**
```go
t := time.NewTicker(1 * time.Second)
defer t.Stop()

for {
    select {
    case <-ctx.Done():
        return
    case <-t.C:
        poll()
    }
}
```

❌ **Wrong: time.After in loop**
```go
for {
    select {
    case <-ctx.Done():
        return
    case <-time.After(1 * time.Second):
        poll()
    }
}
```

## Decision Trees

### Channel vs Mutex

- Need **ownership/serialization** (single writer, message passing) → use **channel + owner goroutine**
- Need **shared cache/map** with many readers and simple updates → use **RWMutex**
- Need **simple counter** with low contention → use **atomic**

### WaitGroup vs errgroup

- Need **error propagation + sibling cancellation** → use **`errgroup.WithContext`**
- Need **only wait** and errors are handled elsewhere → use **`sync.WaitGroup`**

### Buffered vs unbuffered channel

- Need **backpressure** and synchronous handoff → use **unbuffered**
- Need **burst absorption** up to a known size → use **buffered** (size with intent)
- Unsure → start unbuffered and measure; add buffer only to remove known bottleneck

## Testing & Verification

### Race detector and flake control

Run targeted tests with the race detector and disable caching during debugging:

```bash
go test -race ./...
go test -run TestName -race -count=1 ./...
```

### Timeouts to prevent hanging tests

✅ **Correct: test-level timeout via context**
```go
func TestSomething(t *testing.T) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    if err := doThing(ctx); err != nil {
        t.Fatal(err)
    }
}
```

## Troubleshooting

### Symptom: deadlock (test hangs, goroutines blocked)

Actions:
- Add timeouts (`context.WithTimeout`) around blocking operations.
- Verify channel ownership: only the sender closes; receivers stop on `ok == false`.
- Check for missing `<-limit` release in semaphore patterns.

### Symptom: data race (`go test -race` reports)

Actions:
- Identify shared variables mutated by multiple goroutines.
- Add a mutex or convert to ownership model (single goroutine owns state).
- Avoid writing to captured loop variables.

### Symptom: goroutine leak (memory growth, slow shutdown)

Actions:
- Ensure every goroutine selects on `ctx.Done()`.
- Ensure `time.Ticker` is stopped and channels are closed by senders.
- Avoid `context.Background()` inside request paths; propagate caller context.

## Resources

- Go Blog: Concurrency patterns: https://go.dev/blog/pipelines
- `errgroup`: https://pkg.go.dev/golang.org/x/sync/errgroup
- Go memory model: https://go.dev/ref/mem

© Claude MPM Team · MIT · bobmatnyc/claude-mpm-skills

Similar skills

All skills for Programming languages →

From the same set66

wordpress-testing-qa A skill for testing WordPress plugins and themes: PHPUnit integration, WP_Mock, PHPCS and CI/CD. Useful when your test setup needs structure. CX Codex +1 413installs golang-observability-opentelemetry A skill for instrumenting Go services: OpenTelemetry for distributed tracing, Prometheus for metrics, slog for structured logging. CX Codex +1 409installs digitalocean-overview This skill maps DigitalOcean accounts, projects, services, and tooling so you can plan a setup through the Control Panel, doctl, REST API, or Terraform. CX Codex +1 408installs hono-validation A skill for validating Hono requests: JSON bodies, forms, query params, headers and cookies. Useful when you want schema types inside the handler. CX Codex +1 408installs golang-database-patterns A skill that walks your coding agent through Go database setup: picking a library, writing queries, running migrations. CX Codex +1 403installs openrouter A skill for wiring OpenRouter into your project: one OpenAI-compatible API to 200+ models, with streaming, routing and fallbacks. Useful when you juggle several LLMs. CX Codex +1 403installs better-auth-authentication A skill for TypeScript apps: sets up email and password sign-in, social providers and email verification with Better Auth. CX Codex +1 389installs kysely A skill that teaches your agent Kysely: types from schema to query result, migrations, transactions. Useful when an ORM gets in the way but untyped raw SQL is worse. CX Codex +1 389installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor