Pointers
Pointers let a function reach back and change the caller's data instead of working on a copy. Go's pointers are far gentler than C's — no pointer arithmetic, garbage-collected memory — but the two operators & (address-of) and * (dereference) unlock efficient, mutable code. This lesson demystifies them once and for all.
Learn Pointers in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Address-of & Dereference
Two operators do all the work. &x gives you the address of x — a value of type *int if x is an int . *p dereferences the pointer: it follows the address to read or write the value living there. Writing through a pointer changes the original variable — that's the whole point.
2️⃣ Pointers as Function Parameters
This is where pointers earn their keep. Go passes everything by value — a function receives a copy, so editing a plain parameter never touches the caller's variable. Pass a pointer instead and the function can modify the original by dereferencing it. Compare the two functions below: only the pointer version's change survives.
3️⃣ new, nil & Structs
The built-in new(T) allocates a zeroed T and returns a *T . Every pointer's zero value is nil , and dereferencing a nil pointer panics — so guard with a check when a pointer might be unset. A lovely convenience: with a pointer to a struct, Go lets you write p.Field and auto-dereferences for you — no need for the clunky (*p).Field .
Your turn — take an address, then write through the pointer.
Now pass an address so a function can reset your variable.
🧩 Reorder Challenge
These lines change a variable through a pointer, but they're scrambled. Put them in order so the program prints 42 (assume a func main wraps them).
- fmt.Println(x)
Why: x must exist before you can take its address. p := &x captures that address; *p = 42 writes through the pointer into x . Printing x last shows 42 because the write changed the original variable.
Pro Tips
- 💡 Reach for a pointer to mutate or to avoid copying a big struct — otherwise a plain value is simpler and safer.
- 💡 No pointer arithmetic in Go. You can't do p + 1 like in C — Go pointers only point, which removes a whole class of bugs.
- 💡 Let the GC handle memory. Returning &localVar from a function is safe — Go keeps it alive automatically.
- 💡 Always nil-check a pointer that might be unset before dereferencing it, or you'll hit a runtime panic.
Common Errors (and the fix)
- "invalid memory address or nil pointer dereference" — you dereferenced a nil pointer. Initialise it (with new , &something , or a literal) and nil-check before using.
- "cannot take the address of ..." — you tried & on something with no address, like a map element or a literal. Store it in a variable first.
- Function change doesn't stick — you passed a value, not a pointer. Use a *T parameter and call with &x to mutate the caller's data.
- "mismatched types int and *int" — you forgot to dereference. Compare *p == 5 , not p == 5 .
📋 Quick Reference
Operation
Syntax
Meaning
Address-of
p := &x
p is *int pointing at x
Dereference (read)
v := *p
value at p
Dereference (write)
*p = 9
changes the original
Allocate
p := new(int)
zeroed *int
Zero value
var p *int
nil
Struct field
p.Field
auto-dereferenced
🧠 Quick Recall
Predict the output before you reveal the answer.
8 — p points at x , so writing *p = 8 changes the original x to 8.
func f(n int) { n = 0 }; x := 4; f(x); fmt.Println(x)
4 — f takes n by value, so it edits a copy. The original x is unchanged. (Pass *int to mutate it.)
true — the zero value of any pointer type is nil . Dereferencing it would panic, so always check first.
Frequently Asked Questions
Two main reasons: to let a function mutate the caller's value, and to avoid copying a large struct on every call. For small, read-only values, a plain value is clearer.
Much safer. There's no pointer arithmetic, memory is garbage-collected, and returning the address of a local variable is perfectly fine — Go keeps it alive for you.
Q: Why can I write p.Field instead of (*p).Field ?
Go automatically dereferences a struct pointer when you access a field or call a method, purely as a convenience. Both forms mean the same thing.
Mini-Challenge: Deposit Through a Pointer
Write a deposit(a *Account, amount int) that adds to a.Balance , then call it with &acc . Because you pass the address, the change is visible in main . Write it yourself and match the example output.
🎉 Lesson Complete!
- ✅ &x takes an address; *p dereferences to read or write the value
- ✅ Writing through a pointer changes the original variable
- ✅ Pass a *T parameter (and call with &x ) so a function can mutate the caller's data
- ✅ new(T) allocates a zeroed value; every pointer's zero value is nil
- ✅ Go auto-dereferences struct fields: p.Field works directly — and there's no pointer arithmetic
- ✅ Next lesson: Checkpoint — combine everything you've learned so far
Practice quiz
What does the & operator do in Go?
- Dereferences a pointer
- Takes the address of a variable
- Multiplies values
- Declares a pointer type
Answer: Takes the address of a variable. &x yields a pointer holding the memory address of x.
What does the * operator do when applied to a pointer value?
- Takes its address
- Dereferences it to read or write the pointed-to value
- Frees memory
- Compares pointers
Answer: Dereferences it to read or write the pointed-to value. *p dereferences the pointer to access the value it points to.
What is the zero value of a pointer?
- 0
- nil
- An empty struct
- Undefined
Answer: nil. An uninitialized pointer is nil.
What happens if you dereference a nil pointer?
- Returns 0
- Returns nil
- Runtime panic
- Compile error
Answer: Runtime panic. Dereferencing a nil pointer causes a runtime panic (invalid memory address).
After p := &x; *p = 20, what is the value of x?
- 1
- 20
- 0
- nil
Answer: 20. Writing through the pointer with *p = 20 changes the original variable x to 20.
How can a function modify the caller's variable?
- Pass it by value
- Take a *T parameter and call with &x
- Return it only
- Use a global
Answer: Take a *T parameter and call with &x. Pass a pointer (*T) and call with the address (&x) so the function mutates the original.
What does new(T) return?
- A value of type T
- A pointer to a zeroed T
- nil
- An error
Answer: A pointer to a zeroed T. new(T) allocates a zeroed T and returns a *T pointing to it.
Does Go support pointer arithmetic like C (p++)?
- Yes
- No
- Only for slices
- Only with unsafe
Answer: No. Ordinary Go has no pointer arithmetic — pointers can't be incremented.
Given p *Account, how do you read a struct field through it?
- (*p).Field only
- p.Field — Go auto-dereferences
- p->Field
- Field(p)
Answer: p.Field — Go auto-dereferences. Go auto-dereferences struct pointers, so p.Field works directly.
Passing a large struct by pointer instead of by value mainly avoids:
- Type errors
- Copying the whole struct
- Imports
- Panics
Answer: Copying the whole struct. A pointer passes an address, avoiding a full copy of the struct's data.
Continue this course
- Previous: Maps
- Next: Checkpoint: Go Basics