← Back to Go Course | Chapter 8: Pointers | Lesson 6 of 6

Pointers vs Values: When to Use Which

Choosing a pointer or a value is like deciding whether to hand someone the real steering wheel or just a photo of it -- pick a pointer when they need to actually change something.

When Mutation Is Needed

If a function or method is meant to change the caller's original data, it must take a pointer -- passing by value only ever operates on a copy, so any changes are lost once the function returns.

Example: When Mutation Is Needed

markup
package main

import "fmt"

type Account struct {
	Balance float64
}

func (a *Account) Withdraw(amount float64) {
	a.Balance -= amount
}

func main() {
	acc := Account{Balance: 200}
	acc.Withdraw(50)
	fmt.Println(acc.Balance)
}

When Copying Is Fine

For small, simple values -- like a Point with two ints -- copying is cheap and often makes code easier to reason about, since the caller doesn't need to worry about unexpected mutation through a shared pointer.

Example: When Copying Is Fine

markup
package main

import "fmt"

type Point struct {
	X, Y int
}

func translate(p Point, dx, dy int) Point {
	return Point{X: p.X + dx, Y: p.Y + dy}
}

func main() {
	original := Point{1, 1}
	moved := translate(original, 2, 3)
	fmt.Println("original:", original, "moved:", moved)
}

Avoiding Copies of Large Structs

For a struct with many fields, passing it by value copies every field on every function call, which adds up in hot code paths -- passing a pointer instead avoids that copying cost, at the price of sharing mutable state.

Note: A rule of thumb: use pointers for structs larger than a few machine words, or whenever the struct might grow.

Example: Avoiding Copies of Large Structs

markup
package main

import "fmt"

type Report struct {
	Title   string
	Lines   []string
	Total   float64
}

func summarize(r *Report) string {
	return fmt.Sprintf("%s: %d lines, total %.2f", r.Title, len(r.Lines), r.Total)
}

func main() {
	r := &Report{Title: "Sales", Lines: []string{"a", "b"}, Total: 99.5}
	fmt.Println(summarize(r))
}
Common Mistakes
  1. Defaulting to pointers everywhere for performance on tiny structs, when copying a small value is often just as fast and simpler to reason about.
  2. Mixing value and pointer receivers on the same type's methods inconsistently, which can cause confusing behavior with interfaces.
  3. Passing a large struct by value in a hot loop, causing unnecessary copying overhead that a pointer would avoid.
Chapter Summary
  • Use a pointer when a function needs to modify the caller's original value.
  • Use a pointer to avoid copying large structs, improving performance.
  • Use a plain value for small, simple data where mutation isn't needed -- it's simpler to reason about.
  • Keep receiver types consistent across a single type's methods, favoring pointer receivers if any method needs to mutate.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.