Lesson 7 of 30 intermediate 5 min read

Before this:Values, variables & types

Pointers

Key takeaways A pointer holds the address of a value. &x takes the address; *p dereferences it to reach the value. Go has pointers but no pointer arithmetic, so you get sharing and mutation without the classic memory bugs. Use them to let a function modify its caller’s value or to avoid copying a big struct. A pointer to nothing is nil.

Go passes everything by value — a function gets its own copy of each argument. This lesson explains the escape hatch: pointers, which let a function reach back and change the original.

What is a pointer?

A pointer is a value whose contents are the memory address of another value. Two operators do all the work:

snr := 12.5
p := &snr        // p is a *float64 — the address of snr
fmt.Println(*p)  // 12.5 — dereference to read the value
*p = 9.0         // write through the pointer
fmt.Println(snr) // 9 — snr itself changed

& means “address of”; * in front of a pointer means “the value it points at.” *float64 is read as “pointer to float64.”

p (*float64) 0x40c0 snr (float64) 9.0
A pointer stores an address; dereferencing with *p follows the arrow to the value.

Why do pointers matter? Modifying the caller

Because arguments are copied, a plain function can’t change what the caller passed:

func reset(x int)  { x = 0 }        // changes only the copy
func clear(p *int) { *p = 0 }       // changes the caller's value

n := 5
reset(n); fmt.Println(n)   // 5 — unchanged
clear(&n); fmt.Println(n)  // 0 — cleared through the pointer

This is why methods that mutate a struct use a pointer receiver — the subject of Structs & methods.

Avoiding copies of big structs

A Receiver in GopherTrunk might hold filter state, buffers, and AGC settings. Passing it by value copies all of that on every call. Passing *Receiver copies only the address:

func (r *Receiver) Step(sample complex64) {
    r.agc.Apply(&sample)   // mutates r's state in place
}

Rule of thumb: small, read-only values go by value; things you must mutate, or large structs, go by pointer.

The nil pointer

A pointer that points at nothing is nil — the zero value for any pointer type. Dereferencing a nil pointer panics, so guard it when it might be unset:

if cfg.Filter != nil {
    cfg.Filter.Apply(buf)
}

Go has no pointer arithmetic, so a pointer is always either nil or a valid address of a real value — never a hand-computed offset into memory. That is what makes Go pointers safe to use everywhere.

Quick check: what does the * operator do in front of a pointer?

Recap

  • A pointer holds a value’s address; & takes it, * dereferences it.
  • Go has no pointer arithmetic — a pointer is always nil or a valid address.
  • Use pointers to modify the caller’s value or to avoid copying big structs.
  • The zero pointer is nil; dereferencing nil panics, so guard it.

Next up: grouping data with structs and attaching behaviour with methods.

Frequently asked questions

Does Go have pointer arithmetic like C?

No. You can take a pointer with & and follow it with *, but you cannot add to or subtract from an address. That single restriction removes an entire class of memory-corruption bugs while keeping the useful part of pointers: sharing and mutating a value.

When should a function take a pointer instead of a value?

Take a pointer when the function must modify the caller’s value, or when the value is a large struct you don’t want to copy on every call. For small values that only get read, passing by value is simpler and just as fast.