What is the difference between value types and reference-like types in Go?
Quick Answer
Structs and arrays are value types: assigning one variable to another, or passing one to a function, copies the entire value. Slices, maps, channels, and functions are reference-like: their variable is a small header (or pointer) referencing shared underlying data, so copying the header doesn't copy the underlying data. This means func modify(s []int) can mutate the caller's underlying array through indices, but func modify(arr [5]int) only ever modifies its own local copy. Pointers (*T) let you opt any value type into reference-like behavior explicitly, which is exactly why Go methods often take a pointer receiver when they need to mutate the receiver.
Detailed Answer
Getting this distinction wrong is the single most common source of "why didn't my change show up" bugs in Go.
Value types copy on assignment
type Point struct{ X, Y int }
p1 := Point{1, 2}
p2 := p1 // full copy
p2.X = 99
fmt.Println(p1.X) // still 1 — p1 was never touched
func modify(arr [3]int) {
arr[0] = 99 // modifies the local copy only
}
a := [3]int{1, 2, 3}
modify(a)
fmt.Println(a) // [1 2 3] — unchanged
Reference-like types share underlying data
func modify(s []int) {
s[0] = 99 // writes through to the shared backing array
}
sl := []int{1, 2, 3}
modify(sl)
fmt.Println(sl) // [99 2 3] — changed
Table of behavior
| Type | Copy behavior on assignment/pass |
|---|---|
struct, array | Full copy of all fields/elements |
| slice, map, channel | Header/reference copied; underlying data shared |
| pointer | Address copied; both variables point to the same value |
| function value | Reference to the function; safe to copy and pass around |
Opting a struct into reference-like behavior
func modify(p *Point) {
p.X = 99 // dereferences and writes through the pointer
}
pt := Point{1, 2}
modify(&pt)
fmt.Println(pt.X) // 99
This is exactly why a method meant to mutate its receiver needs a pointer receiver — a value receiver method only ever sees a copy.