Pointers
A pointer holds the memory address of a value rather than the value itself. Through a pointer, a function can change a variable that belongs to its caller, and large structs can be shared without copying. Go's pointers are safe — there's no pointer arithmetic and memory is garbage-collected — so they're far less fearsome than in C. This lesson covers & and *, nil pointers, pointers to structs, new, and when to use pointers.
Address and dereference
&x gives the address of x, a value of type *T ("pointer to T"). *p reads or writes the value the pointer points to:
package main import "fmt" func main() { score := 10 p := &score // p points to score fmt.Printf("%T\n", p) fmt.Println(*p) // read through the pointer *p = 25 // write through the pointer fmt.Println(score) q := p // copying a pointer: both point to score *q += 5 fmt.Println(score, *p, p == q) }
*int 10 25 30 30 true
Printing p itself would show a hexadecimal address such as 0x14000012345, which differs from run to run.
Changing a caller's variable
Arguments are copied, so to let a function modify a variable, pass its address:
package main import "fmt" func double(n *int) { *n *= 2 } func swap(a, b *string) { *a, *b = *b, *a } func main() { x := 21 double(&x) fmt.Println(x) first, second := "left", "right" swap(&first, &second) fmt.Println(first, second) }
42 right left
The same is how fmt.Scan(&name) in lesson 13 fills in your variables.
Pointers to structs
Pointers are most often used with structs. Go lets you access fields through a pointer without writing the *: p.Name is shorthand for (*p).Name:
package main import "fmt" type Player struct { Name string Score int } func addPoints(p *Player, pts int) { p.Score += pts // automatic dereference } func main() { ana := Player{Name: "Ana"} addPoints(&ana, 10) addPoints(&ana, 5) fmt.Println(ana.Name, ana.Score) ben := &Player{Name: "Ben", Score: 3} // a pointer to a new struct addPoints(ben, 7) fmt.Printf("%+v\n", *ben) }
Ana 15
{Name:Ben Score:10}nil pointers
The zero value of a pointer is nil: it points to nothing. Dereferencing a nil pointer panics, so check pointers that might be nil:
package main import "fmt" type Config struct{ Port int } func port(c *Config) int { if c == nil { return 8080 // a sensible default } return c.Port } func main() { fmt.Println(port(&Config{Port: 9000}), port(nil)) var c *Config fmt.Println(c.Port) // nil dereference }
9000 8080 panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x104d4f5a4] goroutine 1 [running]: … exit status 2
Nil pointer dereferences are the most common panic in Go programs. A pointer is often used precisely to mean "this value is optional", so make the nil case part of your design.
new
The built-in new allocates a variable and returns a pointer to it. new(T) gives a pointer to a zero value; since Go 1.26 you can also pass an expression, and the new variable starts with that value:
package main import "fmt" type Profile struct { Name string Age *int // optional: nil means "unknown" } func main() { n := new(int) // *int pointing to 0 *n = 3 fmt.Println(*n) withAge := Profile{Name: "Ana", Age: new(34)} noAge := Profile{Name: "Ben"} for _, p := range []Profile{withAge, noAge} { if p.Age != nil { fmt.Println(p.Name, *p.Age) } else { fmt.Println(p.Name, "age unknown") } } }
3 Ana 34 Ben age unknown
Before Go 1.26, filling an optional pointer field like Age needed a temporary variable (age := 34; p.Age = &age) or a helper function. new(34) does it in one expression.
Returning pointers to local variables
In C, returning the address of a local variable is a serious bug. In Go it's perfectly safe: the compiler notices that the variable outlives the function and allocates it on the garbage-collected heap instead of the stack:
package main import "fmt" type Counter struct{ n int } func newCounter(start int) *Counter { c := Counter{n: start} return &c // safe: c lives on after the function returns } func main() { c := newCounter(5) c.n++ fmt.Println(c.n) }
6
When to use pointers
| Use a pointer when… | Example |
|---|---|
| A function must modify the caller's value | func (a *Account) Deposit(n int) |
| The struct is large and copying it would be wasteful | Structs with many fields or big arrays |
| The value must be shared, with one identity | A database connection pool, a cache |
| "No value" must be distinguishable from a zero value | Optional fields: *int, *time.Time |
Don't use pointers for small values that aren't modified, and never to slices, maps or channels just to "avoid copying" — those are already small headers referring to shared data.
No pointer arithmetic
Unlike C, you can't add to a pointer to move through memory: p++ on a pointer doesn't compile. Use slices and indexes instead. (The unsafe package exists for rare low-level needs, but as its name warns, it gives up Go's safety guarantees.)
Try it yourself
Write applyRaise, which gives every employee in a slice a percentage raise. Should it take []Employee or []*Employee? Try both.
Ana 54080 Ben 49400