go-performance — Claude Code skill: speed up Go code
A skill that points your agent at Go hot paths: strconv over fmt, capacity hints for slices and maps, strings.Builder in loops.
npx skills add cxuu/golang-skills --skill go-performance -a claude-codeWhat go-performance does
The skill kicks in when you ask to optimize Go code, dig into a slow spot, or write benchmarks. It carries a set of patterns: strconv instead of fmt for primitive conversion, converting a fixed string to []byte once outside the loop, capacity hints in make for slices and maps, and passing values rather than pointers. Each pattern comes with measured numbers — a slice with reserved capacity runs 0.21s over 100M iterations against 2.48s without it.
Three resources sit behind it. scripts/bench-compare.sh runs when you compare benchmark results, save a baseline, or emit JSON benchmark metadata. references/BENCHMARKS.md is for writing benchmarks, using benchstat, and profiling with pprof. references/STRING-OPTIMIZATION.md covers string conversion, concatenation, and byte/string boundaries. The skill itself says to apply these only on the hot path and not to optimize prematurely.
Concurrent performance patterns are out of scope — goroutines and sync.Pool live in the separate go-concurrency skill. Before keeping a change, the skill asks you to measure the impact with bash scripts/bench-compare.sh and keep only optimizations with a visible gain. That is the point: a micro-optimization that moves nothing on real data should not survive review.
What it helps with
- swap
fmt.Sprintforstrconv.Itoain a hot spot - hoist a
[]byteconversion out of a loop - preallocate a slice before a run of
appendcalls - build a loop string with
strings.Builderinstead of+= - compare benchmarks before and after a change
Example prompts for the agent
- look at the hot path in this handler and tell me what to speed up
- this loop builds a string with
+=, rewrite it withstrings.Builder - run the benchmarks before and after and show me the difference
How to install go-performance
1Skills CLI (Vercel)
npx skills add cxuu/golang-skills --skill go-performance -a claude-code2Manually: put the files in place
~/.claude/skills/go-performance/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/go-performance/
1Skills CLI (Vercel)
npx skills add cxuu/golang-skills --skill go-performance -a codex2Manually: put the files in place
.agents/skills/go-performance/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add cxuu/golang-skills --skill go-performance -a cursor2Manually: put the files in place
.cursor/skills/go-performance/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install cxuu/golang-skills go-performance2Manually: put the files in place
.github/skills/go-performance/SKILL.md1Skills CLI (Vercel)
npx skills add cxuu/golang-skills --skill go-performance -a gemini-cli2Platform CLI
gemini skills install https://github.com/cxuu/golang-skills --path skills/go-performanceManually: put the files in place
.devin/skills/go-performance/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add cxuu/golang-skills --skill go-performance -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about go-performance
How do I install go-performance in Claude?
Run npx skills add cxuu/golang-skills --skill go-performance -a claude-code. The skill file then sits at ~/.claude/skills/go-performance/SKILL.md.
What does go-performance do?
It gives the agent a set of Go optimization patterns: strconv over fmt, []byte outside loops, container capacity hints, passing values. It also points to benchmarking and profiling references.
Does it cover goroutines and concurrency?
No. Concurrent performance patterns are handled by the separate go-concurrency skill. This one stays with single-threaded patterns and benchmarks.
Source text
SKILL.md · Apache-2.0
---
name: go-performance
description: Use when optimizing Go code, investigating slow performance, or writing performance-critical sections. Also use when a user mentions slow Go code, string concatenation in loops, or asks about benchmarking, even if the user doesn't explicitly mention performance patterns. Does not cover concurrent performance patterns (see go-concurrency).
allowed-tools: Bash(bash:*)
---
# Go Performance Patterns
## Resource Routing
- `scripts/bench-compare.sh` - Run when comparing benchmark results, saving baselines, or producing JSON benchmark metadata.
- `references/BENCHMARKS.md` - Read when writing benchmarks, using benchstat, or profiling with pprof.
- `references/STRING-OPTIMIZATION.md` - Read when optimizing string conversion, concatenation, or byte/string boundaries.
Performance-specific guidelines apply only to the **hot path**. Don't prematurely optimize—focus these patterns where they matter most.
---
## Prefer strconv over fmt
When converting primitives to/from strings, `strconv` is faster than `fmt`:
```go
s := strconv.Itoa(rand.Int()) // ~2x faster than fmt.Sprint()
```
| Approach | Speed | Allocations |
|----------|-------|-------------|
| `fmt.Sprint` | 143 ns/op | 2 allocs/op |
| `strconv.Itoa` | 64.2 ns/op | 1 allocs/op |
---
## Avoid Repeated String-to-Byte Conversions
Convert a fixed string to `[]byte` once outside the loop:
```go
data := []byte("Hello world")
for b.Loop() { // Go 1.24+; use b.N loops only for older Go
w.Write(data) // ~7x faster than []byte("...") each iteration
}
```
---
## Prefer Specifying Container Capacity
Specify container capacity where possible to allocate memory up front. This minimizes subsequent allocations from copying and resizing as elements are added.
### Map Capacity Hints
Provide capacity hints when initializing maps with `make()`:
```go
m := make(map[string]os.DirEntry, len(files))
```
**Note**: Unlike slices, map capacity hints do not guarantee complete preemptive allocation—they approximate the number of hashmap buckets required.
### Slice Capacity
Provide capacity hints when initializing slices with `make()`, particularly when appending:
```go
data := make([]int, 0, size)
```
Unlike maps, slice capacity is **not a hint**—the compiler allocates exactly that much memory. Subsequent `append()` operations incur zero allocations until capacity is reached.
| Approach | Time (100M iterations) |
|----------|------------------------|
| No capacity | 2.48s |
| With capacity | 0.21s |
The capacity version is **~12x faster** due to zero reallocations during append.
---
## Pass Values
Don't pass pointers as function arguments just to save a few bytes. If a function refers to its argument `x` only as `*x` throughout, then the argument shouldn't be a pointer.
```go
func process(s string) { // not *string — strings are small fixed-size headers
fmt.Println(s)
}
```
**Common pass-by-value types**: `string`, `io.Reader`, small structs.
**Exceptions**:
- Large structs where copying is expensive
- Small structs that might grow in the future
---
## String Concatenation
Choose the right strategy based on complexity:
| Method | Best For |
|--------|----------|
| `+` | Few strings, simple concat |
| `fmt.Sprintf` | Formatted output with mixed types |
| `strings.Builder` | Loop/piecemeal construction |
| `strings.Join` | Joining a slice |
| Backtick literal | Constant multi-line text |
---
## Benchmarking and Profiling
Always measure before and after optimizing. Use Go's built-in benchmark framework and profiling tools.
```bash
go test -bench=. -benchmem -count=10 ./...
```
> **Validation**: After applying optimizations, run `bash scripts/bench-compare.sh` to measure the actual impact. Only keep optimizations with measurable improvement.
---
## Quick Reference
| Pattern | Bad | Good | Improvement |
|---------|-----|------|-------------|
| Int to string | `fmt.Sprint(n)` | `strconv.Itoa(n)` | ~2x faster |
| Repeated `[]byte` | `[]byte("str")` in loop | Convert once outside | ~7x faster |
| Map initialization | `make(map[K]V)` | `make(map[K]V, size)` | Fewer allocs |
| Slice initialization | `make([]T, 0)` | `make([]T, 0, cap)` | ~12x faster |
| Small fixed-size args | `*string`, `*io.Reader` | `string`, `io.Reader` | No indirection |
| Simple string join | `s1 + " " + s2` | (already good) | Use `+` for few strings |
| Loop string build | Repeated `+=` | `strings.Builder` | O(n) vs O(n²) |
---
## Related Skills
- **Data structures**: See [go-data-structures](../go-data-structures/SKILL.md) when choosing between slices, maps, and arrays, or understanding allocation semantics
- **Declaration patterns**: See [go-declarations](../go-declarations/SKILL.md) when using `make` with capacity hints or initializing maps and slices
- **Concurrency**: See [go-concurrency](../go-concurrency/SKILL.md) when parallelizing work across goroutines or using sync.Pool for buffer reuse
- **Style principles**: See [go-style-core](../go-style-core/SKILL.md) when deciding whether an optimization is worth the readability cost
© cxuu · Apache-2.0 · cxuu/golang-skills