Knaph

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 of x" — produces a pointer to x.
  • *p — "give me the value at the address p points 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 changed

p'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) // 35

Without 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 dereference

This 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.

Sign in to track your progress through this course.

Ask an AI

Open a ready-made prompt in ChatGPT or Claude — just press Enter.

SummarizeChatGPTClaude
Ask me questionsChatGPTClaude
Let's learn togetherChatGPTClaude