Pointers in Go: Addresses, Pass by Value and Pointer Receivers
& and *, why Go always passes by value, what "reference types" really means, nil pointers, and when to use pointers.
On this page
A pointer holds the memory address of a value. Go pointers are simple and safe: there is no pointer arithmetic, and the garbage collector manages memory. You need them for two reasons: to let a function modify the caller's data, and to avoid copying large values.
& and *
&x gives you the address of x.*p gives you the value at the address stored in p (dereferencing).y := 20
p := &y // p has type *int and stores the address of y
fmt.Println(p) // 0xc000012345 (some address)
fmt.Println(*p) // 20
*p = 30 // write through the pointer
fmt.Println(y) // 30*&y is just y: take the address, then read the value at it.
Go always passes by value
When you call a function, Go copies every argument. Changing the copy does not affect the caller:
func double(n int) { n *= 2 }
n := 5
double(n)
fmt.Println(n) // 5, unchangedTo modify the caller's variable, pass its address and write through the pointer:
func doublePtr(n *int) { *n *= 2 }
doublePtr(&n)
fmt.Println(n) // 10Even here Go passes by value: it copies the pointer (the address). Both copies point at the same int.
Pointers to arrays and structs
Passing a large array copies all of it. Passing a pointer copies 8 bytes:
func print(nums *[3]int) {
fmt.Println(nums[0]) // Go automatically dereferences for indexing
}
s := [3]int{1, 2, 3}
print(&s)Structs work the same way, and Go auto-dereferences field access, so p.Name works whether p is a struct or a pointer to a struct (no -> like C).
type Person struct{ Name string }
func rename(p *Person, name string) {
p.Name = name // same as (*p).Name = name
}new(T) allocates a zero T and returns a *T. In practice &Person{Name: "A"} is more common.
Pointer receivers
The same rule applies to methods. A value receiver gets a copy; a pointer receiver can modify the original.
type Person struct{ firstName string }
func (p Person) renameValue(n string) { p.firstName = n } // changes a copy
func (p *Person) renamePointer(n string) { p.firstName = n } // changes the original
p := Person{"Jane"}
p.renameValue("A")
fmt.Println(p.firstName) // Jane
p.renamePointer("B")
fmt.Println(p.firstName) // BNotice we called p.renamePointer on a value, not (&p).renamePointer. Go takes the address automatically when the variable is addressable.
"Reference types": a more precise mental model
You will often read "slices, maps and channels are reference types". Strictly, Go has no reference types: everything is passed by value. But some values contain pointers internally:
| Type | What gets copied | Effect of the copy |
|---|---|---|
| int, float, bool, string, struct, array | the whole value | independent copy |
| pointer | the address | shares the target |
| slice | header {ptr, len, cap} | shares elements; append may or may not be visible |
| map, channel | a pointer to runtime data | shares all data |
| function | a pointer to code (+ captured vars) | shares captured state |
This explains the surprising slice case:
func appendTo(s []int) {
s = append(s, 4) // cap was 3: allocates a NEW array, s now points elsewhere
s[0] = 100 // modifies the new array, not the caller's
}
s := []int{1, 2, 3}
appendTo(s)
fmt.Println(s) // [1 2 3]Strings contain a pointer too, but they are immutable, so sharing is invisible.
nil pointers
The zero value of a pointer is nil. Dereferencing nil panics:
var p *Person
fmt.Println(p == nil) // true
fmt.Println(p.Name) // panic: invalid memory address or nil pointer dereferenceCheck for nil at boundaries, and prefer returning (value, error) over returning a nil pointer that callers might forget to check.
When to use pointers
nil.sync.Mutex or similar that must not be copied.Do not use pointers just to "be efficient" with small values like int or small structs. Copying them is cheap, and values avoid heap allocations and nil checks.
Key takeaways
& takes an address, * reads or writes through it.nil panics, so guard your boundaries.Last updated on