Complete Guide to Pointers in Go
Quick Reference
// Declaration
var p *int // Declare pointer to int (nil by default)
x := 42 // Regular variable
p = &x // & operator: get address of x
value := *p // * operator: get value at address (dereferencing)
// Common Operations
*p = 100 // Modify value through pointer
fmt.Println(&x) // Print memory address
fmt.Println(*p) // Print value at address
// Create & Initialize
p = new(int) // Allocate memory for int, return pointer
p = &int(100) // Create int and get its pointer
// Struct Pointers
type Person struct {
Name string
}
person := &Person{"Alice"} // Create struct and get pointer
fmt.Println(person.Name) // Auto-dereferencing for structs
Understanding Pointers from First Principles
What is a Pointer?
Think of memory like a giant apartment building:
- Each apartment (memory location) has an address
- Each apartment can store one value
- A pointer is like having someone's address written down
- When you have their address, you can:
- Go there to see what's inside (dereferencing)
- Go there to change what's inside (modifying through pointer)
x := 42 // Create an "apartment" with 42 in it
p := &x // Write down this apartment's address
fmt.Println(*p) // Go to address, report what's inside (42)
*p = 100 // Go to address, put 100 inside instead
The Core Operations
-
Getting an Address (&
)
x := 42
p := &x // p now holds x's memory address
Think: "Give me the address of x"
-
Following an Address (*
)
value := *p // Get the value at p's address
*p = 100 // Change the value at p's address
Think: "Go to this address and..."
-
Declaring Pointer Types (*Type
)
var p *int // p can hold the address of an int
var s *string // s can hold the address of a string
Think: "This variable holds the address of a..."
Why Use Pointers?
1. Modifying Function Parameters
In Go, everything is pass-by-value. Pointers let you modify the original:
// Without pointer (changes are lost)
func birthday(age int) {
age++
}
// With pointer (changes persist)
func birthdayPtr(age *int) {
*age++
}
myAge := 30
birthday(myAge) // myAge is still 30
birthdayPtr(&myAge) // myAge is now 31
2. Efficient Memory Usage
Passing large structs by pointer avoids copying:
type HugeStruct struct {
Data [1000000]int
}
// Efficient: just passes a memory address
func processHuge(h *HugeStruct) {
// Work with h
}
huge := &HugeStruct{}
processHuge(huge)
3. Optional Values with nil
Pointers can be nil
, making them perfect for optional values:
type Config struct {
Port int
Timeout *int // Optional timeout
}
// No timeout specified
config1 := Config{Port: 8080}
// With timeout
timeout := 30
config2 := Config{
Port: 8080,
Timeout: &timeout,
}
// Safe usage
if config.Timeout != nil {
fmt.Printf("Timeout set to: %d\n", *config.Timeout)
}
Common Patterns and Techniques
1. Constructor Functions
Return pointers to prevent unnecessary copying:
type Person struct {
Name string
Age int
}
func NewPerson(name string, age int) *Person {
return &Person{
Name: name,
Age: age,
}
}
person := NewPerson("Alice", 30)
2. Method Receivers
Choose between pointer and value receivers:
type Counter struct {
value int
}
// Value receiver (changes don't persist)
func (c Counter) Display() {
fmt.Println(c.value)
}
// Pointer receiver (changes persist)
func (c *Counter) Increment() {
c.value++
}
3. Working with Slices and Maps
Remember: slices and maps are already reference types:
// No pointer needed for basic operations
func addToSlice(s []int, val int) {
s = append(s, val) // WARNING: This might not work as expected!
}
// Use pointer if you need to modify the slice header
func addToSlicePtr(s *[]int, val int) {
*s = append(*s, val) // This works correctly
}
Advanced Topics and Gotchas
1. Pointer Arithmetic
Go doesn't allow pointer arithmetic (unlike C):
p := &x
p++ // Compilation error!
2. Pointer to Pointer
Sometimes you need a pointer to a pointer:
var x int = 42
var p *int = &x
var pp **int = &p
fmt.Println(**pp) // Prints 42
3. Interface Implementation
Methods with pointer receivers only satisfy interfaces when using pointers:
type Incrementer interface {
Increment()
}
type Number struct {
value int
}
// Pointer receiver
func (n *Number) Increment() {
n.value++
}
var i Incrementer
n := Number{value: 42}
i = &n // Works
i = n // Compilation error!
Best Practices
Do:
✅ Use pointers for methods that modify receivers
✅ Use pointers for large structs to avoid copying
✅ Use pointers for optional values (can be nil)
✅ Check for nil before dereferencing
✅ Use pointer receivers consistently in types
Don't:
❌ Use pointers for small structs or basic types unnecessarily
❌ Return pointers to loop variables
❌ Forget to check for nil when it's a possibility
❌ Use pointers just because you can
Safety Features in Go
Unlike C, Go provides several safety features:
- No pointer arithmetic
- No direct memory access
- Garbage collection
- Type safety
- Nil pointer checks
Common Mistakes and Solutions
1. The Loop Variable Trap
// WRONG
var ptrs []*int
for i := 0; i < 3; i++ {
ptrs = append(ptrs, &i) // All pointers will point to the same address
}
// RIGHT
for i := 0; i < 3; i++ {
val := i
ptrs = append(ptrs, &val)
}
2. Returning Local Variables
// WRONG
func createPointer() *int {
x := 42
return &x // Actually safe in Go! The variable escapes to heap
}
// ALSO FINE
func createPointer() *int {
return new(int)
}
3. Nil Pointer Dereference
// WRONG
func process(p *int) {
fmt.Println(*p) // Might panic!
}
// RIGHT
func process(p *int) {
if p == nil {
return
}
fmt.Println(*p)
}
Conclusion
Pointers in Go provide a powerful way to manage memory and share data efficiently. While they might seem intimidating at first, Go's safety features make them much safer to use than in languages like C. Remember:
- Use pointers when you need to modify values through functions
- Use pointers for large structs to avoid copying
- Use pointers for optional values that can be nil
- Always check for nil when there's a possibility
- Don't overuse pointers - Go's built-in types often provide what you need
The key is finding the right balance - use pointers when they provide clear benefits, but don't make your code needlessly complex by using them everywhere.