1. go
  2. /data structures
  3. /pointers

Understanding Pointers in Go Programming

Pointers are a fundamental concept in Go that allow you to work directly with memory addresses. This guide covers everything you need to know about using pointers effectively and safely.

Pointer Basics

Declaring Pointers

// Declare pointer
var p *int

// Create pointer to variable
x := 42
p = &x

// Short declaration
ptr := &x

// Zero value of pointer is nil
var p *int  // p == nil

Dereferencing Pointers

x := 42
p := &x

// Get value through pointer
value := *p  // 42

// Modify value through pointer
*p = 21
fmt.Println(x)  // 21

Working with Pointers

Function Parameters

// Pass by value
func increment(x int) {
    x++  // Only modifies local copy
}

// Pass by pointer
func incrementPtr(x *int) {
    *x++  // Modifies original value
}

x := 42
increment(x)     // x is still 42
incrementPtr(&x) // x is now 43

Struct Pointers

type Person struct {
    Name string
    Age  int
}

// Create struct pointer
p := &Person{
    Name: "Alice",
    Age:  30,
}

// Access fields (both syntaxes work)
fmt.Println((*p).Name)  // Alice
fmt.Println(p.Name)     // Alice (shorthand)

// Modify through pointer
p.Age = 31

Memory Management

New Function

// Allocate memory
p := new(int)    // Creates pointer to zero value
*p = 42

// Allocate struct
person := new(Person)
person.Name = "Bob"
person.Age = 25

Garbage Collection

// Memory is automatically managed
func createLargeArray() *[1000000]int {
    arr := new([1000000]int)
    // Fill array...
    return arr
} // Memory is freed when no longer referenced

Best Practices

1. When to Use Pointers

// Good: Large structs
type LargeStruct struct {
    Data [1000000]int
}

func process(s *LargeStruct) {
    // Work with pointer to avoid copying
}

// Good: Need to modify value
func modify(s *string) {
    *s = "modified"
}

// Avoid: Small values
func bad(x *int) int {
    return *x + 1
}

// Better: Pass by value
func good(x int) int {
    return x + 1
}

2. Nil Checking

// Good: Check for nil
func process(p *Person) error {
    if p == nil {
        return errors.New("nil person")
    }
    // Process person...
    return nil
}

// Good: Defensive programming
func safeDereference(p *int) int {
    if p == nil {
        return 0
    }
    return *p
}

3. Method Receivers

type Counter struct {
    value int
}

// Value receiver (copy)
func (c Counter) Value() int {
    return c.value
}

// Pointer receiver (reference)
func (c *Counter) Increment() {
    c.value++
}

Common Patterns

1. Builder Pattern

type PersonBuilder struct {
    person *Person
}

func NewPersonBuilder() *PersonBuilder {
    return &PersonBuilder{person: &Person{}}
}

func (b *PersonBuilder) Name(name string) *PersonBuilder {
    b.person.Name = name
    return b
}

func (b *PersonBuilder) Age(age int) *PersonBuilder {
    b.person.Age = age
    return b
}

func (b *PersonBuilder) Build() *Person {
    return b.person
}

// Usage
person := NewPersonBuilder().
    Name("Alice").
    Age(30).
    Build()

2. Optional Parameters

type Config struct {
    Host    string
    Port    int
    Timeout time.Duration
}

type Option func(*Config)

func WithTimeout(t time.Duration) Option {
    return func(c *Config) {
        c.Timeout = t
    }
}

func NewServer(opts ...Option) *Server {
    config := &Config{
        Host: "localhost",
        Port: 8080,
    }
    
    for _, opt := range opts {
        opt(config)
    }
    
    return &Server{config: config}
}

3. Safe Pointer Operations

// Safe pointer dereferencing
func getValue(p *string) string {
    if p == nil {
        return ""
    }
    return *p
}

// Safe pointer comparison
func comparePointers(a, b *int) bool {
    if a == nil || b == nil {
        return a == b
    }
    return *a == *b
}

Performance Considerations

1. Memory Access

// Direct access is faster
value := x

// Pointer access has overhead
value := *p

// But pointers are better for large structs
func process(data *LargeStruct) {
    // Avoid copying large data
}

2. Escape Analysis

// Stack allocation (fast)
func stackAlloc() int {
    x := 42
    return x
}

// Heap allocation (slower)
func heapAlloc() *int {
    x := 42
    return &x  // x escapes to heap
}

Common Mistakes

1. Returning Local Variable Address

// Wrong: Returning stack address
func wrong() *int {
    x := 42
    return &x  // Looks dangerous but actually safe in Go
}

// Better: Be explicit
func better() *int {
    x := new(int)
    *x = 42
    return x
}

2. Nil Pointer Dereference

// Wrong: No nil check
func wrong(p *Person) string {
    return p.Name  // Panics if p is nil
}

// Right: Check for nil
func right(p *Person) string {
    if p == nil {
        return ""
    }
    return p.Name
}

3. Unnecessary Indirection

// Wrong: Unnecessary pointer
type Config struct {
    name *string
    age  *int
}

// Right: Use value types unless pointer needed
type Config struct {
    name string
    age  int
}

Next Steps

  1. Learn about slices
  2. Explore interfaces
  3. Study memory model
  4. Practice with concurrency

Additional Resources