Skip to content

Pointers

A pointer is a variable that holds the memory address of another variable. The type of a pointer to T is written *T.

x := 42
p := &x // & takes the address of x; p has type *int

To read or write through a pointer, dereference it with *:

fmt.Println(*p) // 42 — read through p
*p = 100 // write through p — x is now 100
fmt.Println(x) // 100
OperatorReads asEffect
&x”address of x”produces a *T pointing to x
*p”value at p”dereferences p, reads or writes T

1. Mutation across function boundaries. Go passes all arguments by value. If a function needs to modify the caller’s variable, pass a pointer:

func double(n *int) {
*n = *n * 2
}
x := 5
double(&x)
fmt.Println(x) // 10

2. Large structs. Copying a large struct on every call is wasteful. Pass *Config instead of Config when the struct has many fields.

3. Optional values. A *T can be nil to represent “not set”, similar to null in other languages.

When you have a pointer to a struct, Go automatically dereferences it for field access. You do not need to write (*cfg).Debug:

cfg := &Config{Workers: 4}
cfg.Debug = true // shorthand for (*cfg).Debug = true

The built-in new(T) allocates a zero value of type T and returns *T:

n := new(int) // *int pointing to 0
*n = 7

The zero value of any pointer type is nil. Dereferencing a nil pointer causes a runtime panic:

var p *int
fmt.Println(p == nil) // true
// *p = 1 would panic: nil pointer dereference

Always check a pointer for nil before dereferencing when it might not have been set.

Go deliberately omits pointer arithmetic. You cannot do p++ or p + 4 to walk through memory. This eliminates an entire class of memory-safety bugs. The unsafe package provides pointer arithmetic as an escape hatch, but it is almost never needed in application code.

package main
import "fmt"
type Config struct {
Debug bool
Workers int
}
func enableDebug(cfg *Config) {
cfg.Debug = true // mutates the caller's Config
}
func double(n *int) {
*n = *n * 2
}
func main() {
// & takes the address; * dereferences
x := 42
p := &x
fmt.Println(*p) // 42
*p = 100
fmt.Println(x) // 100 -- x was mutated through p
// Pointer for mutation across function boundary
double(&x)
fmt.Println(x) // 200
// Pointer to struct
cfg := Config{Debug: false, Workers: 4}
enableDebug(&cfg)
fmt.Println(cfg.Debug) // true
// new() allocates a zeroed value and returns a pointer
n := new(int)
*n = 7
fmt.Println(*n) // 7
// nil pointer
var ptr *int
fmt.Println(ptr == nil) // true
}
You pass `x` (an int) to `func add(n int)` which sets `n = n + 1`. After the call, what is `x`?
What does `&x` produce?
What happens if you dereference a nil pointer in Go?
Given `cfg := &Config{Workers: 4}`, how do you set the Debug field?