Pointers
Pointers are one of Go's most important and practical features. They let you work directly with memory addresses โ passing references to data rather than copies, and enabling functions to modify variables that live outside their own scope.
Memory Addresses and the & Operator
Every variable in your program lives at a specific location in memory. The address-of operator & returns that location as a pointer value.
The value of p is not 42 โ it is the address where 42 is stored. On most 64-bit systems, a pointer is 8 bytes regardless of what it points to.
The * Operator: Dereferencing
To read or write the value that a pointer points to, use the dereference operator *. This follows the pointer to the actual variable.
Modifying *p is exactly the same as modifying n directly. The pointer is just a path that leads back to the original variable.
Pointer Types
A pointer's type encodes both the fact that it is a pointer and what type it points to. The type *int is "pointer to int", *string is "pointer to string", and so on.
The type system prevents you from, for example, assigning a *string where a *int is expected โ type safety applies to pointers just as it does to regular values.
Why Use Pointers?
1. Modify a caller's variable
Without pointers, a function receives a copy of its arguments. Changes to that copy never escape back to the caller.
2. Avoid copying large structs
When a struct has many fields, passing it by value copies every single field on every call. Passing a pointer copies just the 8-byte address โ far cheaper for large types.
The Zero Value of a Pointer: nil
An uninitialized pointer has the zero value nil, which means it points to nothing. Dereferencing a nil pointer causes a runtime panic โ one of the most common mistakes in Go.
Always guard before dereferencing a pointer that might be nil:
This pattern โ check, then dereference โ is the standard defense against nil pointer panics.
Allocating with new
The built-in new(T) function allocates memory for a zero-value T and returns a *T. It is handy when you want a pointer without first declaring a named variable.
In practice, new is used less often than you might expect. Most of the time, you either take the address of an existing variable with &, or use a struct literal with & to get a pointer to a new struct value.
Value Receivers vs Pointer Receivers (Preview)
When you define methods on a struct, the receiver can be a value or a pointer. This is the same trade-off as regular function parameters:
- Value receiver
(s MyStruct): the method gets a copy. Reading only, or for small structs. - Pointer receiver
(s *MyStruct): the method can mutate the original. Required when the method must change fields.
Go automatically takes the address of c when you call a pointer receiver method on an addressable value, so c.Increment() compiles and works even though c is not declared as a *Counter. You will explore this fully in the Structs module.
Pointer Indirection in Method Calls
Go quietly bridges the gap between value and pointer receivers at call sites. When you call a pointer-receiver method on an addressable value variable, Go rewrites the call as (&v).Method() for you. The reverse also applies: a value-receiver method called on a pointer is rewritten as (*p).Method().
The addressability constraint is the key limit: Go can only auto-take the address of a variable, not of an expression that produces a temporary value. A composite literal used directly as a call target is not addressable, so Counter{}.Inc() is a compile error โ there is no variable whose address Go can use.
The practical takeaway: declare your receiver as a variable (c := Counter{}) and Go handles the & and * mechanics transparently.
Choosing a Value or Pointer Receiver
The Tour of Go gives clear guidance on which receiver type to choose. The decision affects correctness, performance, and interface satisfaction.
Use a pointer receiver when:
- The method must modify the receiver. A value receiver gets a copy; mutations are discarded when the method returns.
- The struct is large. Copying a large struct on every call wastes CPU and stack space; a pointer costs only 8 bytes.
- Consistency. If any method on a type uses a pointer receiver, all methods should โ mixing creates confusion about which calls mutate the original and which do not, and it breaks interface satisfaction in subtle ways.
Use a value receiver when:
- The method only reads the receiver and the type is small enough that copying is cheap.
- You want call-site immutability โ callers pass a value and the method cannot affect their copy.
- The type is a primitive alias (e.g.,
type Celsius float64) where pointer overhead is not justified.
The consistency rule in practice
When a type satisfies an interface, Go requires that the method set matches. A pointer receiver method is only in the method set of *T, not T โ so keeping all methods on pointer receivers avoids the common mistake of passing a value where a pointer is needed to satisfy an interface.
Knowledge Check
What does `&x` return?
How do you read the value at a pointer `p`?
You have `var c Counter` and `func (c *Counter) Inc()`. How do you call Inc?