10 Common Go Mistakes That Kill Performance and How to Fix Them
This article lists ten typical Go programming pitfalls—from undefined enum values and misleading benchmarks to pointer misuse, slice initialization, error handling, context misuse, and goroutine closures—explaining why they hurt performance or correctness and offering concrete, idiomatic solutions.
This article summarizes ten Go language bad practices that can degrade performance and correctness, providing concrete fixes.
Unknown Enum Values
Using iota to create an enum can cause the zero value to be interpreted as a valid state; when a JSON request omits the enum field, the struct field defaults to zero, unintentionally mapping to StatusOpen. Define an explicit StatusUnknown as the first enum value to avoid this.
type Status uint32
const (
StatusUnknown Status = iota
StatusOpen
StatusClose
)Performance Testing
Benchmarks can be misleading when the compiler inlines or eliminates functions without side effects. In the example, clear is optimized away, producing inaccurate timing. Prevent this by storing the result in a global variable so the compiler cannot prove the function has no side effects.
var result uint64
func BenchmarkCorrect(b *testing.B) {
var r uint64
for i := 0; i < b.N; i++ {
r = clear(1221892080809121, 10, 63)
}
result = r
}Pointers Everywhere
Value passing creates a copy on the stack, while pointer passing copies only the address. Stack variables are fast because they require no garbage collection or synchronization. Use value passing unless you need to share mutable state across goroutines.
func getFooValue() foo {
var result foo
// Do something
return result
}
func getFooPointer() *foo {
var result foo
// Do something
return &result
}Eliminate for/switch or for/select Pitfalls
A plain break exits only the innermost switch or select, not the surrounding loop. Use a labeled break to exit the outer loop.
loop:
for {
select {
case <-ch:
// do something
case <-ctx.Done():
break loop
}
}Error Management
Wrap errors with context using pkg/errors so each layer adds meaningful information while preserving the original cause. When handling errors, either log them or propagate them, but not both.
func postHandler(customer Customer) Status {
err := insert(customer.Contract)
if err != nil {
log.WithError(err).Errorf("unable to serve HTTP POST request for customer %s", customer.ID)
return Status{ok: false}
}
return Status{ok: true}
}
func insert(contract Contract) error {
err := dbQuery(contract)
if err != nil {
return errors.Wrapf(err, "unable to insert customer contract %s", contract.ID)
}
return nil
}Slice Initialization
If the final length is known, pre‑allocate the slice with that length for speed; otherwise, allocate with zero length and the required capacity and use append. Both approaches are valid, the first is slightly faster.
func convert(foos []Foo) []Bar {
bars := make([]Bar, len(foos))
for i, foo := range foos {
bars[i] = fooToBar(foo)
}
return bars
}
func convert(foos []Foo) []Bar {
bars := make([]Bar, 0, len(foos))
for _, foo := range foos {
bars = append(bars, fooToBar(foo))
}
return bars
}Context Management
context.Contextcarries deadlines, cancellation signals, and key‑value data across API boundaries. Combine a parent context with a timeout to automatically cancel long‑running operations.
ctx, cancel := context.WithTimeout(parent, 100*time.Millisecond)
response, err := grpcClient.Send(ctx, request)Don’t Overuse -race Option
The Go race detector is useful for finding data races but does not replace proper concurrent design; enable it selectively during testing.
Use io.Reader Instead of Filenames
Accept an io.Reader so the function works with files, HTTP responses, or in‑memory buffers, simplifying testing.
func count(reader *bufio.Reader) (int, error) {
count := 0
for {
line, _, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
return count, nil
}
return 0, errors.Wrapf(err, "unable to read")
}
if len(line) == 0 {
count++
}
}
}Goroutine and Loop Variable
Launching a goroutine inside a range loop captures the same loop variable, often printing the final value repeatedly. Pass the variable as a parameter or create a new variable inside the loop to capture the current value.
for _, i := range ints {
go func(v int) {
fmt.Printf("%v
", v)
}(i)
}
// or
for _, i := range ints {
i := i // new variable
go func() {
fmt.Printf("%v
", i)
}()
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
