Pointers & the Address-of Operator
Pointers hold the address of a value, enabling mutation across function boundaries and avoiding large copies.
Go has pointers but no pointer arithmetic, keeping memory safety straightforward.
Summary
&x takes the address of x; *p dereferences pointer p.
Function arguments are passed by value; share mutation by passing pointers.
Method receivers can be values or pointers; pointer receivers are required when methods modify state.
Recipe
Quick-reference recipe card - copy-paste ready.
package main
import "fmt"
func increment(n *int) {
*n++
}
func main() {
count := 10
increment(&count)
fmt.Println(count)
}When to reach for this:
- Mutate caller state from a function.
- Avoid copying large structs on hot paths (profile first).
- Represent optional or nullable fields with
*T. - Implement methods that modify receiver fields.
Working Example
package main
import "fmt"
type Config struct {
Host string
Port int
}
func (c *Config) SetPort(p int) {
c.Port = p
}
func clone(cfg Config) Config {
return cfg // struct copied by value
}
func main() {
cfg := Config{Host: "localhost", Port: 8080}
cfg.SetPort(9090)
copy := clone(cfg)
copy.Port = 1
fmt.Println(cfg.Port, copy.Port)
var p *int
if p != nil {
fmt.Println(*p)
} else {
fmt.Println("p is nil")
}
}What this demonstrates:
- Pointer receiver
SetPortmutatescfgin place. - Struct assignment copies values -
copy.Portdoes not affectcfg. - Nil pointer check before dereference avoids panic.
&is not needed when calling pointer-receiver methods on addressable values.
Deep Dive
How It Works
- Variables live in stack or heap based on escape analysis, not on whether you take
&. new(T)allocates zeroedTand returns*T; idiomatic code often uses&T{}.nilpointers compare equal to each other and to untyped nil in typed pointer context.- Interfaces holding typed nil pointers are not equal to
nilinterface.
Value vs Pointer Passing
| Aspect | By value T | By pointer *T |
|---|---|---|
| Copy cost | Whole struct | Pointer word |
| Mutation in callee | No (unless return) | Yes via dereference |
nil possible | No for value | Yes |
| JSON omitempty | N/A | Omits nil pointers |
Receiver Selection
| Use pointer receiver when | Use value receiver when |
|---|---|
| Method mutates receiver | Small immutable type |
| Struct is large | Consistency with existing API |
| Must share mutex state | Zero-value methods must work (time.Time) |
Go Notes
// Optional field
type User struct {
Name string
Nick *string // nil means unset
}
// Do not pass pointer to loop variable without copying
for i := 0; i < 3; i++ {
n := i
go func() { fmt.Println(&n) }()
}Gotchas
- Nil pointer dereference -
*pwhenp == nilpanics. Fix: checkp != nilor guarantee construction. - Typed nil in interfaces -
return (*T)(nil), errstill makes interface non-nil. Fix: return nil interface or concrete value type. - Pointer to map/slice header - Rarely needed; slices/maps already mutate through shared header. Fix: pass slice/map directly.
- Taking address of range variable - Pre-1.22 loop vars reused one address. Fix: copy
item := iteminside loop. - Overusing pointers - Small structs cost less to copy than indirection and GC pressure. Fix: pass by value until profiling says otherwise.
- Mutating through pointer to const data - Cannot point at untyped const; must point at variables.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Return new value | Immutable transforms | Large structs or many mutations |
Pointer *T | Mutation, optional fields | Tiny ints/bools (use value) |
| Interface | Hide concrete type | Need nil vs value distinction clearly |
sync/atomic | Concurrent counters | General struct fields |
FAQs
Does Go have pointer arithmetic?
No - you cannot increment pointers like in C.
Use slices for buffer traversal.
When is & automatic for method calls?
Go automatically takes address for pointer receivers when value is addressable: cfg.SetPort(1) calls (&cfg).SetPort(1).
Can I get a pointer to a map value?
No - &m[key] is invalid because map storage may move.
Copy to a local variable and point at that if needed temporarily.
What does new return?
new(T) returns *T to a zero-initialized T.
make is only for slices, maps, and channels.
Are pointers comparable?
Pointer values compare with == and != if they point to the same variable or both nil.
They do not compare slice contents.
How do pointers interact with escape analysis?
Taking &local may escape local to the heap if the pointer outlives the function.
The compiler decides; you cannot force stack allocation reliably.
Should JSON fields use pointers?
*string with omitempty omits unset fields in JSON.
Use pointers for optional API fields; values for required ones.
What is a double pointer **T?
Pointer to pointer - rare in application code.
Appears in C interop or some generic APIs.
Can functions return pointers to locals?
Yes - escape analysis moves locals to the heap when their address escapes.
This is safe in Go unlike stack-returning in C.
How do I reset a struct pointer field?
Set to nil or replace with &T{} for fresh zero value.
Choose based on whether absent vs empty matters to callers.
Why use pointer receivers on sync.Mutex?
Mutex must not be copied after first use.
Pointer receivers on types embedding Mutex prevent accidental copy by value methods.
What is unsafe.Pointer?
Low-level escape hatch for interop and advanced optimizations.
Avoid in application code unless you truly need unsafe package rules.
Related
- Structs, Embedding & Field Promotion - Pointer receivers on composed types
- Zero Values and Initialization - Nil pointers and
new - Variables, Constants & Scope - Address of loop variables
- Arrays, Slices & Maps at a Glance - When pointers add little value for slices
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).