Pointers — sharing a value without pointer arithmetic
Go keeps pointers from lower-level languages — the ability to refer to a value's actual memory location — but deliberately removes the dangerous part, pointer arithmetic, keeping the useful part: letting a function actually modify the caller's data.
Beginner
3 min read
The problem pointers solve: functions get their own copy by default
In Go, passing a value to a function passes a copy of it by default —
changes made inside the function don't affect the original:
func double(n int) { n = n * 2 // only changes the local copy}x := 5double(x)fmt.Println(x) // still 5 — the function never touched the real x
A pointer is a value that holds the memory address of another value,
letting a function reach back and modify the original instead of a copy:
func double(n *int) { *n = *n * 2 // *n means "the value AT this address" — modifies the real x}x := 5double(&x) // &x means "the address OF x"fmt.Println(x) // 10 — the actual x was modified
The two operators, and what each one means
& ("address of") — turns a value into a pointer to it.
* ("dereference," used two different ways) — as a type, *int
means "a pointer to an int." As an operation, *n means "the value at
the address n holds."
The same symbol doing two related but different jobs (declaring a pointer
type vs. dereferencing one) is a real, common point of confusion the first
time — reading *int as a type declaration and *n as "go look at what
this points to" are the two contexts to keep straight.
What Go deliberately leaves out
Languages like C allow pointer arithmetic — adding a number directly to
a pointer to move to a different memory address, walking through raw memory
by hand. Go's pointers can't do this in ordinary code; a Go pointer can only be
dereferenced or reassigned to point somewhere else, never shifted by an
arbitrary offset (the one escape hatch is the deliberately named unsafe
package, which exists for low-level work and is a red flag anywhere else).
This is a deliberate safety trade-off, matching this
domain's "why Go" theme from lesson 1 — most of the practical value of
pointers (letting a function modify a caller's data, avoiding an expensive
copy of a large struct) doesn't actually require arithmetic on addresses,
and removing it eliminates an entire, historically serious class of memory
bugs.
Why this matters day to day
Recognizing *T as "a pointer to T" and knowing when Go implicitly uses
pointers for you (methods on a struct, covered in the structs/composition
lesson, commonly use a pointer receiver specifically so they can modify the
struct) is essential to reading real Go code — a function signature taking
*User instead of User is communicating, right there in the type, that
it intends to read or modify the actual value the caller passed in, not
just look at a copy.