Pointers
You saw &fahrenheit back in the very first exercise, with a promise to explain it properly later. Here's later.
What a pointer is
A pointer holds the memory address of a value, instead of the value itself. Two operators do all the work:
&x— "give me the address ofx" — produces a pointer tox.*p— "give me the value at the addressppoints to" — dereferences a pointer.
x := 10
p := &x // p is a *int, pointing at x
fmt.Println(*p) // 10
*p = 20 // write through the pointer
fmt.Println(x) // 20 — x itself changedp's type is *int — "pointer to int." Assigning through *p writes to the same memory x lives in, which is why changing *p changes x too.
Why you need this given structs are copied by value
Recall from the last module: assigning or passing a struct copies it. That's often exactly what you want — a copy can't be accidentally mutated by code elsewhere. But sometimes you want a function to modify the caller's actual data. A pointer is how:
type Person struct {
Name string
Age int
}
func birthday(p *Person) {
p.Age++
}
haleh := Person{Name: "Haleh", Age: 34}
birthday(&haleh)
fmt.Println(haleh.Age) // 35Without the pointer, birthday would receive a copy, increment the copy's Age, and the caller's haleh would be untouched. &haleh passes the address instead, so p.Age++ reaches through to the original.
Notice p.Age++, not (*p).Age++ — Go automatically dereferences a pointer when you access a field through it. You only need the explicit * when you're working with the whole value the pointer points to, not one of its fields.
Go still has garbage collection
This is a common early worry, so it's worth saying explicitly: Go is garbage collected, same as Python. You never call free(), there's no manual memory management, and you can return a pointer to a local variable from a function and it just works — Go's compiler figures out that the variable needs to outlive the function call and allocates it accordingly:
func newPerson(name string) *Person {
p := Person{Name: name}
return &p // safe — Go keeps p alive as long as something points to it
}In a language like C, returning the address of a local variable is a classic bug (the stack frame it lived in is gone once the function returns). Go's garbage collector makes that impossible — pointers exist here for control over sharing and mutation, not because you're expected to manage memory by hand.
nil pointers
A pointer's zero value is nil — "points at nothing." Dereferencing a nil pointer panics at runtime:
var p *Person
fmt.Println(p.Name) // panic: invalid memory address or nil pointer dereferenceThis is the Go equivalent of Python's AttributeError: 'NoneType' object has no attribute ... — same root cause (something you expected to be set wasn't), surfacing as a crash instead of a caught exception. Checking if p != nil before dereferencing is the standard defense, exactly like checking if x is not None in Python.
When to use a pointer
A rough rule of thumb, not a hard law: pass a pointer when the function needs to modify the caller's data, or when the value is large enough that copying it repeatedly would be wasteful. For small values you're not modifying — an int, a small struct you're only reading — passing by value is simpler and avoids giving the function more power over your data than it needs.