go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Use when profiling, benchmarking, or optimizing Go code — includes the measure-first methodology, the pprof-driven decision tree (which symptom maps to which fix), allocation reduction, capacity hints, hot-path patterns (strconv vs fmt, repeated string→byte conversions,
Claude Code
$ npx -y skills add muratmirgun/gophers --skill go-performance --agent claude-code
Codex
$ npx skills add muratmirgun/gophers --skill go-performance -a codex -y
Cursor
$ npx skills add muratmirgun/gophers --skill go-performance -a cursor -y
Gemini CLI
$ npx skills add muratmirgun/gophers --skill go-performance -a gemini-cli -y
GitHub Copilot
$ npx skills add muratmirgun/gophers --skill go-performance -a github-copilot -y
OpenCode
$ npx skills add muratmirgun/gophers --skill go-performance -a opencode -y
Windsurf
$ npx skills add muratmirgun/gophers --skill go-performance -a windsurf -y
name: go-performance description: "Use when profiling, benchmarking, or optimizing Go code — includes the measure-first methodology, the pprof-driven decision tree (which symptom maps to which fix), allocation reduction, capacity hints, hot-path patterns (strconv vs fmt, repeated string→byte conversions, strings.Builder), and runtime tuning. Apply proactively whenever a user mentions slowness, allocations, GC pressure, or asks for benchmarks, even if no specific pattern is named." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. Methodology is Go-version-neutral; `b.Loop()` and PGO require Go 1.21+/1.24+." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
Performance work in Go follows one rule: **measure first**. Intuition about bottlenecks is wrong roughly 80% of the time. Profile, hypothesise, change *one thing*, re-measure. The patterns in this skill apply only on hot paths — premature optimisation makes code worse without making it faster.
1. **Profile before optimising.** `go test -bench`, `pprof`, `fgprof` — never guess. 2. **One change at a time.** Multi-change "optimisation" passes are unreviewable. 3. **Compare with `benchstat`.** Single runs lie; you need ≥6 runs to see signal. 4. **Allocation reduction usually beats CPU micro-optimisation** — the GC is fast but not free. 5. **Rule out external bottlenecks first.** If 90% of latency is the DB, faster Go code is irrelevant. 6. **Document optimisations in comments.** Future readers will revert "ugly" code without context.
The cycle is: **define goal → write benchmark → measure baseline → diagnose → improve one thing → re-measure → commit with the diff.**
# baseline go test -bench=BenchmarkHotPath -benchmem -count=6 ./pkg/... | tee /tmp/report-1.txt # (apply ONE change) # compare go test -bench=BenchmarkHotPath -benchmem -count=6 ./pkg/... | tee /tmp/report-2.txt benchstat /tmp/report-1.txt /tmp/report-2.txt
If `benchstat` shows no statistically significant change, the optimisation didn't work — revert it. Keep the `/tmp/report-*.txt` files as an audit trail; paste the `benchstat` output in the commit body.
> Read [references/benchmarking-and-pprof.md](references/benchmarking-and-pprof.md) for benchmark writing, pprof workflow, and `b.Loop()` (Go 1.24+).
Before optimising any Go code, check that the bottleneck is actually in your process:
If the bottleneck is external (DB, downstream API, disk), fix that — query tuning, indexes, connection pools, caching. No Go-level change will help.
| Symptom (from pprof) | Action | |---|---| | High `alloc_objects` / `alloc_space` | reduce allocations (preallocate, pool, struct fields) | | One function dominates CPU profile | inline-friendly rewrite, avoid reflection, simpler algorithm | | High GC% / OOM kills | tune `GOMEMLIMIT`, `GOGC`; reduce live heap | | Goroutines blocked on I/O | concurrency, batching, connection pool tuning | | Same computation many times | memoise / `singleflight` / cache | | Wrong algorithm (O(n²) where O(n) exists) | fix algorithm before anything else | | Mutex profile hot | reduce critical section, sharded locks, `sync.Pool` |
> Read [references/allocation-and-memory.md](references/allocation-and-memory.md) for allocation patterns, `sync.Pool`, struct alignment, and escape analysis.
These are the small changes that consistently show up in profiles. Apply them when the symptom matches — not preemptively.
// Bad — fmt parses a format string s := fmt.Sprint(n) // Good — direct conversion, ~2x faster, half the allocations s := strconv.Itoa(n)
| | ns/op | allocs | |---|---|---| | `fmt.Sprint(n)` | ~143 | 2 | | `strconv.Itoa(n)` | ~64 | 1 |
// Bad — allocates on every iteration
for i := 0; i < n; i++ {
w.Write([]byte("hello"))
}
// Good — convert once
hello := []byte("hello")
for i := 0; i < n; i++ {
w.Write(hello)
}About 7x faster in a tight loop.
// Bad — repeated growth, O(n) copies per growth
out := []Result{}
for _, x := range input {
out = append(out, transform(x))
}
// Good — zero reallocations
out := make([]Result, 0, len(input))
for _, x := range input {
out = append(out, transform(x))
}Slice capacity is **exact**: `make([]T, 0, n)` allocates exactly `n` slots. Map capacity is a **hint** about bucket count, but still avoids the worst rehashes.
| | Time | |---|---| | no capacity | ~2.48s | | with capacity | ~0.21s |
About 12x faster on the synthetic benchmark.
`s += w` in a loop is O(n²). Use `strings.Builder`, with `Grow(n)` when the final size is estimable.
`*string`, `*int`, `*time.Time` add indirection without saving anything — strings and time.Time are already small headers. Use pointers only for mutation, types ~128B+, types embedding sync primitives, or where `nil` is meaningful.
> Read [references/concrete-patterns.md](references/concrete-patterns.md) for the full pattern catalogue with benchmark numbers.
| Anti-pattern | Why it hurts | Do this instead | |---|---|---| | Optimising without `pprof` | wrong target, wasted effort | profile first | | Default `http.Client` for high-throughput callers | `MaxIdleConnsPerHost: 2` bottleneck | configure `Transport` | | Logging inside hot loops | prevents in
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.