Go Performance Patterns
Performance-specific guidelines apply only to the hot path. Don't prematurely optimize—focus these patterns where they matter most.
Available Scripts
scripts/bench-compare.sh— Runs Go benchmarks N times with optional baseline comparison via benchstat. Supports saving results for future comparison. Runbash scripts/bench-compare.sh --helpfor options.
Prefer strconv over fmt
When converting primitives to/from strings, strconv is faster than fmt:
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 |
Read references/STRING-OPTIMIZATION.md when choosing between strconv and fmt for type conversions, or for the full conversion table.
Avoid Repeated String-to-Byte Conversions
Convert a fixed string to []byte once outside the loop:
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data) // ~7x faster than []byte("...") each iteration
}
Prefer Specifying Container Capacity
Specify container capacity where possible to allocate memory up front.
Map Capacity Hints
m := make(map[string]os.DirEntry, len(files))
Slice Capacity
data := make([]int, 0, size)
| 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.
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, or 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 |
Read references/STRING-OPTIMIZATION.md when choosing a string concatenation strategy, using strings.Builder in loops, or deciding between fmt.Sprintf and manual concatenation.
Benchmarking and Profiling
Always measure before and after optimizing. Use Go's built-in benchmark framework and profiling tools.
go test -bench=. -benchmem -count=10 ./...
Read references/BENCHMARKS.md when writing benchmarks, comparing results with benchstat, profiling with pprof, or interpreting benchmark output.
Validation: After applying optimizations, run
bash scripts/bench-compare.shto 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²) |