Avoid These Common Go Pitfalls Before They Crash Your Code

This article compiles a series of frequent Go programming pitfalls—ranging from incorrect use of unsafe.Sizeof and variadic any parameters to slice expansion, pointer handling, closure capture, concurrency bugs, and serialization quirks—providing concrete code examples and safe alternatives to help developers write more reliable Go code.

Baidu Geek Talk
Baidu Geek Talk
Baidu Geek Talk
Avoid These Common Go Pitfalls Before They Crash Your Code

1. Parameter Passing Misuse

Using unsafe.Sizeof on a pointer always returns the size of the pointer (8 bytes on 64‑bit platforms), which can lead to incorrect size calculations.

func TestSizeofPtrBug(t *testing.T) {
    type CodeLocation struct {
        LineNo int64
        ColNo  int64
    }
    cl := &CodeLocation{10, 20}
    size := unsafe.Sizeof(cl)
    fmt.Println(size) // always returns 8
}

Solution: Write a helper that returns the size of the underlying value.

func TestSizeofPtrWithoutBug(t *testing.T) {
    type CodeLocation struct { LineNo int64; ColNo int64 }
    cl := &CodeLocation{10, 20}
    size := ValueSizeof(cl)
    fmt.Println(size) // 16
}

func ValueSizeof(v any) uintptr {
    typ := reflect.TypeOf(v)
    if typ.Kind() == reflect.Pointer {
        return typ.Elem().Size()
    }
    return typ.Size()
}

1.2 Variadic any Parameters

When a variadic parameter has type any, passing a slice without the ellipsis treats the whole slice as a single element.

appendAnyF := func(t []any, toAppend ...any) []any { return append(t, toAppend...) }
emptySlice := []any{}
slice2 := []any{"hello", "world"}
// Bug: appending slice as one element
emptySlice = appendAnyF(emptySlice, slice2)
fmt.Println(emptySlice) // [[hello world]]
// Correct usage with ellipsis
emptySlice = []any{}
emptySlice = appendAnyF(emptySlice, slice2...)
fmt.Println(emptySlice) // [hello world]

1.3 Array Value Semantics

Arrays are passed by value; modifying a parameter does not affect the original array.

arr := [3]int{0, 1, 2}
 f := func(v [3]int) { v[0] = 100 }
 f(arr)
 fmt.Println(arr) // [0 1 2]

1.4 Slice Expansion Breaks Sharing

Appending to a slice may allocate new backing storage, breaking the link with the original array.

arr := []int{0, 1, 2}
 f := func(v []int) {
    v[0] = 100          // modifies original
    v = append(v, 4)    // new allocation
    v[0] = 50           // does NOT modify original
 }
 f(arr)
 fmt.Println(arr) // [100 1 2]

1.5 Returning Shared Slice References

Returning a slice that shares underlying memory can unintentionally mutate the source data; copy the slice before returning.

type Queue struct { content []byte; pos int }
func (q *Queue) ReadUnsafe(size int) []byte {
    if q.pos+size >= len(q.content) { return nil }
    pos := q.pos
    q.pos += size
    return q.content[pos:q.pos]
}
func (q *Queue) ReadSafe(size int) []byte {
    if q.pos+size >= len(q.content) { return nil }
    pos := q.pos
    q.pos += size
    ret := make([]byte, size)
    copy(ret, q.content[pos:q.pos])
    return ret
}

2. Pointer‑Related Pitfalls

2.1 Storing uintptr Values

Saving a pointer as uintptr loses the connection to the actual memory address; the compiler does not track changes.

slice := []int{0, 1, 2}
ptr := unsafe.Pointer(&slice[0])
slice = append(slice, 3) // new allocation
ptr2 := unsafe.Pointer(&slice[0])
fmt.Printf("ptr is %d, ptr2 is %d, equal? %v
", ptr, ptr2, ptr == ptr2)

2.2 len/cap on nil vs empty slices/maps

Both nil and empty slices (or maps) return length and capacity zero.

var s []int = nil
fmt.Println(len(s), cap(s)) // 0 0
s2 := []int{}
fmt.Println(len(s2), cap(s2)) // 0 0
var m map[int]int = nil
fmt.Println(len(m)) // 0
m2 := map[int]int{}
fmt.Println(len(m2)) // 0

2.3 Using new for maps

new(map[int]int)

creates a nil map; attempts to assign to it panic. Use make instead.

mp := new(map[int]int)
func(m map[int]int) { m[10] = 10 }
// panic: assignment to entry in nil map

2.4 Nil Interface vs Nil Pointer Interface

A nil concrete pointer assigned to an interface value is not a nil interface; comparisons to nil are false.

type MyErr struct{}
func (e *MyErr) Error() string { return "" }
var e *MyErr = nil
var e2 error = e // e2 != nil
fmt.Println(e2 == nil) // false

3. Function, Method and Control‑Flow Issues

3.1 Closure Capturing Loop Variable

Closures capture the loop variable itself; after the loop ends the variable holds the final value, causing out‑of‑bounds errors.

type S struct { A, B, C string }
typ := reflect.TypeOf(S{})
funcArr := make([]func() string, typ.NumField())
for i := 0; i < typ.NumField(); i++ {
    f := func() string { return typ.Field(i).Name }
    funcArr[i] = f
}
fmt.Println(funcArr[0]()) // panic: index out of bounds

Fix: copy the index into a new variable inside the loop.

for i := 0; i < typ.NumField(); i++ {
    idx := i
    f := func() string { return typ.Field(idx).Name }
    funcArr[i] = f
}
fmt.Println(funcArr[0]()) // A

3.2 Range on Large Elements

Iterating with range copies each element; for large structs this can be much slower than a manual index loop.

func CreateABigSlice(count int) [][4096]int {
    ret := make([][4096]int, count)
    for i := 0; i < count; i++ { ret[i] = [4096]int{} }
    return ret
}
func BenchmarkRangeHiPerformance(b *testing.B) {
    v := CreateABigSlice(1 << 12)
    for i := 0; i < b.N; i++ {
        var tmp [4096]int
        for k := 0; k < len(v); k++ { tmp = v[k] }
        _ = tmp
    }
}
func BenchmarkRangeLowPerformance(b *testing.B) {
    v := CreateABigSlice(1 << 12)
    for i := 0; i < b.N; i++ {
        var tmp [4096]int
        for _, e := range v { tmp = e }
        _ = tmp
    }
}
// Result: range version ~10,000× slower.

3.3 Defer Inside Loops

Deferring a resource release inside a loop delays the close until the surrounding function returns, potentially exhausting resources.

for i := 0; i < 5; i++ {
    f, err := os.Open("./mygo.go")
    if err != nil { log.Fatal(err) }
    defer f.Close() // delayed
}

Fix: close explicitly inside the loop.

for i := 0; i < 5; i++ {
    f, err := os.Open("/path/to/file")
    if err != nil { log.Fatal(err) }
    f.Close()
}

3.4 Goroutine May Exit Prematurely

A goroutine that panics without recovery terminates the whole program.

go func() { panic("oh...") }()
for i := 0; i < 3; i++ { fmt.Println(i); time.Sleep(time.Second) }
fmt.Println("bye bye!") // never reached

Recover inside the goroutine:

go func() {
    defer func() { recover() }()
    panic("oh...")
}()

4. Concurrency and Memory Synchronization

Go's memory model guarantees write ordering only within a single goroutine; without explicit synchronization, other goroutines may observe stale values.

var msg string
var done bool
func setup() { msg = "hello, world"; done = true }
func main() {
    go setup()
    for !done {}
    println(msg) // may print empty string
}

Use a channel to synchronize:

var msg string
var done = make(chan bool)
func setup() { msg = "hello, world"; done <- true }
func main() {
    go setup()
    <-done
    println(msg) // guaranteed "hello, world"
}

5. Serialization Gotchas

Unmarshalling into a map does not delete existing keys; subsequent unmarshals only add or overwrite keys, leaving stale entries.

val := map[string]int{}
s1 := `{"k1":1,"k2":2,"k3":3}`
s2 := `{"k1":11,"k2":22,"k4":44}`
json.Unmarshal([]byte(s1), &val) // {k1:1 k2:2 k3:3}
json.Unmarshal([]byte(s2), &val) // {k1:11 k2:22 k3:3 k4:44}

Solution: re‑declare the variable before each unmarshal if you need a clean map.

6. Miscellaneous

6.1 Numeric Overflow When Shifting

Shifting a small‑type value before converting can overflow.

var num int16 = 5000
var result int64 = int64(num << 9) // overflow, prints 4096
// Fixed:
var result int64 = int64(num) << 9 // correct 2560000

6.2 Map Iteration Order Is Random

Go maps iterate in a nondeterministic order; do not rely on ordering.

mp := map[int]int{}
for i := 0; i < 20; i++ { mp[i] = i }
for k, v := range mp { fmt.Println(k, v) } // order varies each run
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.

DebuggingconcurrencyprogrammingGobest practicesMemoryPitfalls
Baidu Geek Talk
Written by

Baidu Geek Talk

Follow us to discover more Baidu tech insights.

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.