← 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.
Syntax
go
func modify(v *TypeName) {   // pointer: can change the caller's value
	v.field = newValue
}

func read(v TypeName) {      // value: gets a copy
	// use v
}

modify(&value)
read(value)

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
// Every Go file belongs to a package; main builds an executable
package main

// Import the fmt package
import "fmt"

// Define a struct type named Account
type Account struct {
	Balance float64
}

// Define the method Withdraw on type Account
func (a *Account) Withdraw(amount float64) {
	a.Balance -= amount
}

// main is where the program starts running
func main() {
	// Short declaration: create acc and infer the type
	acc := Account{Balance: 200}
	acc.Withdraw(50)
	// Print the values followed by a newline
	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
// Every Go file belongs to a package; main builds an executable
package main

// Import the fmt package
import "fmt"

// Define a struct type named Point
type Point struct {
	X, Y int
}

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

// main is where the program starts running
func main() {
	// Short declaration: create original and infer the type
	original := Point{1, 1}
	// Short declaration: create moved and infer the type
	moved := translate(original, 2, 3)
	// Print the values followed by a newline
	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
// Every Go file belongs to a package; main builds an executable
package main

// Import the fmt package
import "fmt"

// Define a struct type named Report
type Report struct {
	Title   string
	Lines   []string
	Total   float64
}

// Define the function summarize
func summarize(r *Report) string {
	// Sprintf builds a formatted string instead of printing it
	return fmt.Sprintf("%s: %d lines, total %.2f", r.Title, len(r.Lines), r.Total)
}

// main is where the program starts running
func main() {
	// Short declaration: create r and infer the type
	r := &Report{Title: "Sales", Lines: []string{"a", "b"}, Total: 99.5}
	// Print the values followed by a newline
	fmt.Println(summarize(r))
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
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.