>_
GolangStepByStep
Intern

Pointers

Understand memory addresses, indirection, and when to pass by pointer

# Why Pointers Matter

Pointers are how Go lets multiple parts of your code work with the same underlying valuewithout copying large data structures repeatedly. They are central to method receivers, linked data structures, and mutation-heavy service logic.

Most pointer bugs are not about syntax—they are about ownership and nil handling. If you are clear about which function owns mutation and which function only reads, pointers become predictable and safe.

Real-World Analogyclick to expand

Think of a pointer like a house address. You can hand the address to someone, and they can update what's in the house without moving the house itself. Passing the whole value instead is like photocopying the house contents and mailing the copy.

# Address-of and Dereference

Use &x to get x's address and*p to access the value stored at pointer p.

x := 42
p := &x

fmt.Println(x)  // 42
fmt.Println(p)  // address like 0xc000...
fmt.Println(*p) // 42

*p = 100
fmt.Println(x)  // 100

Breakdown: p stores an address, not a copy of x. Writing through *p mutates the original value.

# Passing Pointers to Functions

Function arguments in Go are always passed by value. Passing a pointer means the copied value is an address, so both caller and callee refer to the same memory location.

func increment(n int) {
    n++
}

func incrementPtr(n *int) {
    *n++
}

x := 5
increment(x)
fmt.Println(x) // 5

incrementPtr(&x)
fmt.Println(x) // 6

# Pointer Receivers on Structs

Use pointer receivers when a method must modify the struct or when copying the struct would be expensive. Go lets you write p.X even when p is a pointer—automatic dereference makes this ergonomic.

type Counter struct {
    Value int
}

func (c *Counter) Inc() {
    c.Value++
}

func main() {
    c := Counter{}
    c.Inc()
    fmt.Println(c.Value) // 1
}

# Nil Pointers and Safe Guards

The zero value of a pointer is nil. Dereferencing a nil pointer panics, so defensive checks are essential in handler/service code that accepts optional pointers.

func printScore(score *int) {
    if score == nil {
        fmt.Println("no score")
        return
    }
    fmt.Println(*score)
}

# Practice Challenge

Implement Swap(a, b *int) so the original caller values are exchanged. Then extend it to return early if either pointer is nil.

package main

import "fmt"

func Swap(a, b *int) {
    // TODO: swap values at pointers
}

func main() {
    x, y := 3, 7
    Swap(&x, &y)
    fmt.Println(x, y) // 7 3
}

⚡ Key Takeaways

  • Pointers store addresses; dereference with *p to read/write data
  • Passing *T lets functions mutate caller-owned values
  • Use pointer receivers for mutation and large structs
  • Always guard against nil before dereferencing
practice & review