Pointers in Go: A Practical Guide with Examples
A pointer stores the memory address of another value. In Go, pointers are useful when a function needs to modify a caller-owned value, when a type needs a meaningful nil state, or when pointer receiver semantics are appropriate.
Declaring a Pointer
var ptr *intThe zero value of a pointer is nil. Use & to take an address and * to dereference it:
value := 42
ptr := &value
fmt.Println(*ptr) // 42Modify a Value Through a Pointer
func updateValue(p *int) {
*p = 100
}
value := 42
updateValue(&value)
fmt.Println(value) // 100updateValue receives the address of value, so assigning through *p changes the original variable.
Pointer Parameters
A pointer parameter is useful when mutation is part of the function’s contract:
func increment(value *int) {
(*value)++
}
num := 10
increment(&num)
fmt.Println(num) // 11Always consider whether mutation makes the API clearer. Returning a new value can be simpler when the function does not need shared mutable state.
Pointers and Struct Methods
Pointer receivers are common when a method modifies a struct or when copying the receiver would be undesirable:
type Counter struct {
Value int
}
func (c *Counter) Increment() {
c.Value++
}
counter := Counter{}
counter.Increment()
fmt.Println(counter.Value) // 1Go automatically takes the address in many ordinary method-call situations, so counter.Increment() works even though Increment has a *Counter receiver.
Pointers Are Not Required for Every Large Value
It is tempting to use pointers purely to avoid copies, but performance depends on escape analysis, allocation behavior, cache locality, and how the value is used. Measure before changing APIs solely for performance.
Slices, maps, channels, functions, and interfaces already contain reference-like descriptors internally and usually do not need a pointer just to share their underlying data.
Nil Pointers
Dereferencing a nil pointer panics:
var ptr *int
fmt.Println(*ptr) // panicCheck for nil when it is a valid state in your API.
Conclusion
Pointers in Go are intentionally simpler than in languages that expose pointer arithmetic. Use them when you need shared mutation, pointer receiver semantics, or an optional value represented by nil. Prefer the clearest ownership and mutation model rather than using pointers automatically everywhere.