Web Analytics

Pointers

Intermediate ~40 min read

Pointers store memory addresses of values. They're essential for efficient programming and understanding how Go manages memory. In this lesson, you'll learn what pointers are, how to use them, and when they're necessary.

What is a Pointer?

A pointer holds the memory address of a value:

Output
Click Run to execute your code
Pointer Operators:
  • & - Address operator (gets the address of a variable)
  • * - Dereference operator (accesses the value at an address)
  • *Type - Pointer type declaration

Memory Visualization

x := 42
p := &x

// Memory layout:
// Variable x: [42]          at address 0x1234
// Variable p: [0x1234]      (stores address of x)
//
// *p accesses the value at address 0x1234, which is 42
Pro Tip: Think of a pointer as a "reference" or "arrow" pointing to where the actual value lives in memory.

Pointer Basics

Declaring Pointers

// Declare a pointer to int
var p *int

// Get address of a variable
x := 42
p = &x

// Declare and initialize
y := 100
ptr := &y

// Zero value of a pointer is nil
var nilPtr *int  // nil

Dereferencing Pointers

Output
Click Run to execute your code
Important: Dereferencing a nil pointer causes a runtime panic! Always check if a pointer is nil before dereferencing.

Pointers and Functions

Pointers allow functions to modify the original value:

Output
Click Run to execute your code
Pass By Value Pass By Pointer
Copies the value Copies the address
Cannot modify original Can modify original
Expensive for large structs Cheap (just an address)
func f(x int) func f(x *int)
Pointers in Go - Memory Addresses and Dereferencing

Figure: Pointers - Understanding memory addresses, address-of operator (&), and dereference operator (*)

Pointers to Structs

Pointers are commonly used with structs:

type Person struct {
    Name string
    Age  int
}

// Create a struct
p := Person{Name: "Alice", Age: 25}

// Get pointer to struct
ptr := &p

// Access fields (Go automatically dereferences)
fmt.Println(ptr.Name)  // Same as (*ptr).Name

// Modify through pointer
ptr.Age = 26
fmt.Println(p.Age)  // 26 (original modified)
Automatic Dereferencing: Go automatically dereferences pointers when accessing struct fields. ptr.Name is shorthand for (*ptr).Name.

new() Function

// new() allocates memory and returns a pointer
p := new(Person)  // *Person, all fields zero-valued

p.Name = "Bob"
p.Age = 30

// Equivalent to:
p := &Person{}  // More common idiom

When to Use Pointers?

โœ… Use Pointers When:

  1. You need to modify the original value
    func increment(x *int) {
        *x++
    }
  2. Working with large structs (avoid copying)
    type LargeStruct struct {
        data [1000000]int
    }
    
    func process(s *LargeStruct) {
        // Efficient: only copies pointer
    }
  3. Need to represent "no value" (nil)
    var optionalValue *int  // nil means "no value"
  4. Implementing methods that modify the receiver
    func (p *Person) Birthday() {
        p.Age++
    }

โŒ Don't Use Pointers When:

  1. Working with small values (int, bool, small structs)
  2. Slices, maps, channels (already reference types)
  3. You don't need to modify the value
Best Practice: Start with values. Use pointers only when you have a specific reason (modification, large structs, or optional values).

Common Pointer Patterns

1. Returning Pointers

// Safe: Go handles memory automatically
func newPerson(name string) *Person {
    p := Person{Name: name}
    return &p  // Safe! Go moves to heap if needed
}

// Usage
person := newPerson("Alice")

2. Pointer to Pointer

x := 42
p := &x   // *int
pp := &p  // **int (pointer to pointer)

fmt.Println(**pp)  // 42

3. Nil Pointer Checks

func process(p *Person) {
    if p == nil {
        fmt.Println("nil pointer")
        return
    }
    fmt.Println(p.Name)
}

Common Mistakes

1. Dereferencing nil pointer

// โŒ Wrong - panic!
var p *int
*p = 42  // panic: nil pointer dereference

// โœ… Correct - initialize first
x := 0
p := &x
*p = 42

2. Taking address of loop variable

// โŒ Wrong - all pointers point to same variable
var ptrs []*int
for _, v := range []int{1, 2, 3} {
    ptrs = append(ptrs, &v)  // All point to same v!
}

// โœ… Correct - create new variable
var ptrs []*int
for _, v := range []int{1, 2, 3} {
    v := v  // Create new variable
    ptrs = append(ptrs, &v)
}

3. Unnecessary pointer to slice/map

// โŒ Wrong - slices are already references
func addItem(s *[]int, item int) {
    *s = append(*s, item)
}

// โœ… Correct - slices don't need pointers
func addItem(s []int, item int) []int {
    return append(s, item)
}

Exercise: Swap Function

Task: Create a swap function using pointers.

Requirements:

  • Create a swap function that swaps two integers
  • Use pointers to modify the original values
  • Test with values 10 and 20
  • Print before and after values
Show Solution
package main

import "fmt"

// swap exchanges the values of two integers
func swap(a, b *int) {
    *a, *b = *b, *a
}

// swapStrings exchanges two strings
func swapStrings(a, b *string) {
    *a, *b = *b, *a
}

func main() {
    // Test with integers
    x, y := 10, 20
    fmt.Printf("Before swap: x=%d, y=%d\n", x, y)
    
    swap(&x, &y)
    fmt.Printf("After swap:  x=%d, y=%d\n", x, y)
    
    // Test with strings
    s1, s2 := "Hello", "World"
    fmt.Printf("\nBefore swap: s1=%s, s2=%s\n", s1, s2)
    
    swapStrings(&s1, &s2)
    fmt.Printf("After swap:  s1=%s, s2=%s\n", s1, s2)
    
    // Bonus: Demonstrate why pointers are needed
    fmt.Println("\n--- Without pointers (doesn't work) ---")
    a, b := 100, 200
    fmt.Printf("Before: a=%d, b=%d\n", a, b)
    
    // This won't work (copies values)
    func(x, y int) {
        x, y = y, x  // Only swaps copies!
    }(a, b)
    
    fmt.Printf("After:  a=%d, b=%d (unchanged!)\n", a, b)
}

Summary

  • Pointers store memory addresses of values
  • & operator gets the address of a variable
  • * operator dereferences (accesses value at address)
  • nil is the zero value for pointers
  • Use pointers to modify original values in functions
  • Automatic dereferencing for struct field access
  • new() allocates and returns a pointer
  • Don't use pointers for slices, maps, or channels

What's Next?

Now that you understand pointers, you're ready to learn about Interfacesโ€”Go's powerful way to define behavior and achieve polymorphism without inheritance!