3. Pass by Reference

Functions in Go generally pass variables by value, meaning that functions receive a copy of most non-composite types:

example.go
func increment(x int) {
    x++
    fmt.Println(x)
    // 6
}


func main() {
    x := 5
    increment(x)
    fmt.Println(x)
    // 5
}

The main function still prints 5 because the increment function received a copy of x.

One of the most common use cases for pointers in Go is to pass variables by reference, meaning that the function receives the address of the original variable, not a copy of the value. This allows the function to update the original variable's value.

example_pointer.go
func increment(x *int) {
    *x++
    fmt.Println(*x)
    // 6
}

func main() {
    x := 5
    increment(&x)
    fmt.Println(x)
    // 6
}

Fields of Pointers

When your function receives a pointer to a struct, you might try to dereference and access a field like this and encounter an error:

Instead, access it β€” like you'd normally do β€” using a selector expression:

This approach is the recommended, simplest way to access struct fields in Go, and is shorthand for:

circle-info

Go automatically dereferences struct pointers when accessing fields, so prefer ptr.Field over (*ptr).Field for readability.

Pass by Value (Default)

When you pass a variable to a function, Go makes a copy:

What happens:

  1. x = 5 in main

  2. increment receives a copy with value 5

  3. The copy becomes 6

  4. Original x in main is still 5

Pass by Reference (Using Pointers)

When you pass a pointer, the function can modify the original:

What happens:

  1. x = 5 in main

  2. &x gets the address of x

  3. increment receives the address

  4. *x++ goes to that address and increments the value

  5. Original x in main is now 6

Visual Comparison

Pass by Value:

Pass by Reference:

Real Example: Updating a User

Without pointer (doesn't work):

With pointer (works):

Accessing Struct Fields Through Pointers

What's Really Happening (Long Form)

Go lets you skip the (*u) and just write u.age as a convenience.

Complete Example

Output:

Accessing Multiple Fields

Common Mistake: Don't Double Dereference

circle-exclamation

When to Use Pointers in Functions

Use pointers when:

  • You need to modify the original variable

  • The variable is large (struct with many fields) and copying is expensive

  • You want to avoid copying data

Don't use pointers when:

  • The variable is small (int, bool, small string)

  • You don't need to modify the original

  • You want the function to be side-effect free

Quick Reference

Scenario
Syntax
Result

Pass value

func(x int) β†’ func(5)

Function gets copy

Pass pointer

func(x *int) β†’ func(&x)

Function can modify original

Access field (simple)

ptr.field

Recommended way

Access field (explicit)

(*ptr).field

Same, but verbose

Modify through pointer

*ptr = value

Changes original

Practice Exercise

Only deposit2 works because it receives a pointer and can modify the original.

Assignment

Write an analyzeMessage function. It should accept a pointer to an Analytics struct and a Message struct (not a pointer).

It should look at the Success field of the Message struct and, based on that, increment the MessagesTotal, MessagesSucceeded, or MessagesFailed fields of the Analytics struct as appropriate.

Solution

chevron-rightWhy It Workshashtag

Function Signature

  • a *Analytics - pointer to Analytics struct

    • Can modify the original struct

    • Changes persist after function returns

  • m Message - value (copy) of Message struct

    • Just reading Success field

    • Don't need to modify, so no pointer needed

Accessing Fields

  • Go auto-dereferences pointers: a.field instead of (*a).field

  • Both use dot notation, but pointer modifies original

Logic Flow

  1. Check if message succeeded or failed

  2. Increment appropriate counter (succeeded or failed)

  3. Always increment total count

Calling the Function

Key Concepts

Concept
Syntax
Purpose

Pointer parameter

a *Analytics

Receive address, can modify original

Value parameter

m Message

Receive copy, read-only

Access pointer field

a.field

Modifies original struct

Access value field

m.field

Reads from copy

Pass pointer

&stats

Send address of variable

Pass value

msg

Send copy of variable

Why Use Pointer for Analytics?

  • Need to modify the struct's fields

  • Changes must persist after function returns

  • Tracking stats requires updating the original

Why Use Value for Message?

  • Only reading the Success field

  • Don't need to modify the message

  • Small struct, copying is cheap