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.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
10 Common Go Mistakes That Kill Performance and How to Fix Them

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.Context

carries 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)
    }()
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceconcurrencyGobest practicesError Handlingcontextslices
MaGe Linux Operations
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.