Why Go Pointers Trip You Up: Common Pitfalls and Deep Dive
This article explains how Go pointers work, why they differ from C pointers, and highlights frequent mistakes such as struct pointer assignments, for‑range loop quirks, closure captures, and memory‑escape scenarios, providing clear code examples and runtime insights.
Like C, Go has pointers that let you pass a variable's memory address instead of the whole value, which can save memory but also introduces pitfalls that developers often encounter.
1. Pointer‑type variables
In Go you obtain a variable's address with the & operator, creating a pointer that stores the address rather than the actual value. Basic type pointers (e.g., int) cannot be assigned directly; you must dereference with * to read or write the value.
func main() {
num := 1
numP := &num
// numP = 2 // compile error: cannot assign to numP
*numP = 2
}Struct pointers behave differently because a struct occupies contiguous memory; accessing testP.Num actually yields a normal variable, allowing direct assignment.
type Test struct { Num int }
func main() {
test := Test{Num: 1}
test.Num = 3 // normal assignment
fmt.Println("v1", test) // v1 {3}
testP := &test
testP.Num = 4 // struct pointer assignment
fmt.Println("v2", test) // v2 {4}
}Slices, maps, and channels are themselves pointer types in Go’s runtime, so you can print their addresses without using &.
func main() {
nums := []int{1,2,3}
fmt.Printf("%p
", nums) // e.g., 0xc0000160c0
fmt.Printf("%p
", &nums[0]) // same address
maps := map[string]string{"aa":"bb"}
fmt.Printf("%p
", maps) // e.g., 0xc000076180
ch := make(chan int, 0)
fmt.Printf("%p
", ch) // e.g., 0xc00006c060
}Internally, creating a slice returns a pointer to the first element; creating a map returns a pointer to an hchan structure. The fmt.Printf implementation detects these pointer‑like types via reflection and prints their underlying addresses.
2. Go only has value passing, not reference passing
When a function receives a pointer, the pointer value itself is copied (value passing), but the copy points to the original data, which is why it appears as “reference passing”.
type User struct { Name string; Age int }
func setNameV1(user *User) { user.Name = "test_v1" }
func setNameV2(user User) { user.Name = "test_v2" }
func main() {
u := User{Name: "init"}
fmt.Println("init", u) // init {init 0}
setNameV1(&u)
fmt.Println("v1", u) // v1 {test_v1 0}
setNameV2(u)
fmt.Println("v2", u) // v2 {test_v1 0}
}The same principle applies to slices, maps, and channels.
3. for‑range and pointers
When using for range with a pointer to the loop variable, the same address is reused each iteration, causing all appended pointers to reference the last element.
type User struct { Name string; Age int }
func main() {
userList := []User{{"aa",1},{"bb",1}}
var newUser []*User
for _, u := range userList {
newUser = append(newUser, &u) // all pointers point to the same u
}
for _, nu := range newUser {
fmt.Printf("%+v
", nu.Name)
}
}Printing the address inside the loop shows the address remains constant.
point: 0xc00000c030
val: aa
point: 0xc00000c030
val: bbA similar issue appears in goroutine closures that capture the loop variable’s address.
func main() {
for i := 0; i < 10; i++ {
go func(idx *int) { fmt.Println("go:", *idx) }(&i)
}
time.Sleep(5 * time.Second)
}4. Closures and pointers
A closure that captures a pointer variable will see changes made to that variable outside the closure.
func incr1(x *int) func() {
return func() { *x = *x + 1; fmt.Printf("incr point x = %d
", *x) }
}
func incr2(x int) func() {
return func() { x = x + 1; fmt.Printf("incr normal x = %d
", x) }
}
func main() {
x := 1
i1 := incr1(&x)
i2 := incr2(x)
i1(); i2(); i1(); i2()
x = 100
i1(); i2(); i1(); i2()
}5. Pointers and memory escape
Memory escape occurs when a pointer forces a value onto the heap, increasing GC pressure. Common escape scenarios include returning a pointer to a local variable, storing a pointer to a variable that has already escaped, and placing pointers inside slices, maps, or channels.
type Escape struct { Num1 int; Str1 *string; Slice []int }
func NewEscape() *Escape { return &Escape{} } // &Escape{} escapes to heap
func main() {
e := &Escape{Num1: 0}
name := "aa"
e.Str1 = &name // name moves to heap because e escaped
arr := make([]*int, 2)
n := 10
arr[0] = &n // pointer stored in slice also escapes
}Understanding these patterns helps you write more efficient Go code.
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.
