Pointers in Go

Shiraz Khan's Avatar

Shiraz Khan

LinkedIn

Senior Software Engineer

31 January 2025 / 5 min read

Pointers are a fundamental concept in Go (Golang) that allow you to work directly with memory addresses. They are powerful but can be tricky if not understood properly. Below is a detailed explanation of everything you need to know about pointers in Go.

What is a Pointer?

A pointer is a variable that stores the memory address of a value. Instead of holding the actual value, a pointer holds the location where the value is stored in memory.

Every variable in Go is stored at a specific memory location. A pointer points to this location.

Declaring Pointers

In Go, you declare a pointer using the * symbol followed by the type of the variable it points to.

var p *int // p is a pointer to an int

Here, p is a pointer to an integer. Initially, it holds the zero value for pointers, which is nil.

Getting the Address of a Variable

You can get the memory address of a variable using the & operator.

x := 42
p := &x // p now holds the memory address of x

Dereferencing a Pointer

Dereferencing in Go means accessing the value stored at the memory address a pointer holds.

To access the value stored at the memory address a pointer holds, use the * operator.

fmt.Println(*p) // Prints 42 (the value of x)

Pointer Zero Value

The zero value of a pointer is nil. A nil pointer does not point to any memory location.

var p *int
fmt.Println(p) // Prints <nil>

Attempting to dereference a nil pointer will cause a runtime panic.

Pointer Arithmetic

Go does not support pointer arithmetic (unlike C/C++). You cannot increment or decrement pointers to navigate through memory.

Pointers and Functions

Pointers are often used in functions to modify the original value of a variable.

Pass by Value

By default, Go passes arguments to functions by value (a copy is made).

func modifyValue(val int) {
    val = 100
}

x := 42
modifyValue(x)
fmt.Println(x) // Prints 42 (unchanged)

Pass by Reference

To modify the original variable, pass a pointer to the function.

func modifyPointer(val *int) {
    *val = 100
}

x := 42
modifyPointer(&x)
fmt.Println(x) // Prints 100 (changed)

Pointers and Structs

Pointers are commonly used with structs to avoid copying large structs and to modify struct fields.

type Person struct {
    Name string
    Age  int
}

func (p *Person) UpdateAge(newAge int) {
    p.Age = newAge
}

person := Person{Name: "Mark", Age: 30}
person.UpdateAge(31)
fmt.Println(person.Age) // Prints 31

Pointer to a Pointer

You can have a pointer that points to another pointer.

x := 42
p := &x
pp := &p

fmt.Println(*pp) // Prints the address of x
fmt.Println(**pp) // Prints 42

Pointers and Slices

Slices in Go are reference types, meaning they already contain a pointer to an underlying array. You rarely need to use pointers with slices.

slice := []int{1, 2, 3}
p := &slice
fmt.Println((*p)[0]) // Prints 1

Pointers and Maps

Like slices, maps are reference types. You don’t typically need pointers to modify maps.

m := map[string]int{"foo": 1}
p := &m
fmt.Println((*p)["foo"]) // Prints 1

Pointers and Interfaces

When working with interfaces, pointers can be used to satisfy the interface.

type Speaker interface {
    Speak() string
}

type Dog struct{}

func (d *Dog) Speak() string {
    return "Woof!"
}

var s Speaker = &Dog{}
fmt.Println(s.Speak()) // Prints "Woof!"

Common Pitfalls with Pointers

  1. Dereferencing a nil Pointer: This will cause a runtime panic.

    var p *int
    fmt.Println(*p) // Panic: nil pointer dereference
  2. Memory Leaks: If you hold onto pointers unnecessarily, the garbage collector cannot free the memory.

  3. Uninitialized Pointers: Always initialize pointers before using them.

Best Practices

  1. Use pointers when you need to modify the original variable.
  2. Avoid pointers for small or immutable data types (e.g., int, string).
  3. Prefer passing structs by pointer to avoid copying large structs.
  4. Be cautious with nil pointers to avoid runtime panics.

Pointer Safety

Go’s garbage collector ensures that pointers are safe to use as long as they point to valid memory.

Example: Swapping Values Using Pointers

Here’s a classic example of swapping two values using pointers.

func swap(a, b *int) {
    *a, *b = *b, *a
}

x, y := 10, 20
swap(&x, &y)
fmt.Println(x, y) // Prints 20, 10

Summary

Frequently Asked Questions

  1. Why use pointers?

    • To modify the original variable.
    • To avoid copying large data structures.
    • To share data efficiently.
  2. When not to use pointers?

    • For small or immutable data types.
    • When you don’t need to modify the original variable.
  3. Are pointers safe in Go?

    • Yes, Go’s garbage collector ensures memory safety, but you must avoid nil pointer dereferencing.

Code Examples

Example 1: Basic Pointer Usage

package main

import "fmt"

func main() {
    x := 42
    p := &x
    fmt.Println(*p) // Prints 42
}

Example 2: Modifying a Struct Using a Pointer

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func (p *Person) Birthday() {
    p.Age++
}

func main() {
    person := Person{Name: "Alice", Age: 30}
    person.Birthday()
    fmt.Println(person.Age) // Prints 31
}

Conclusion

Pointers are a powerful feature in Go that allow you to work directly with memory addresses. They are essential for efficient memory management and modifying data outside a function’s scope. However, they require careful handling to avoid common pitfalls like nil pointer dereferencing.

By understanding and using pointers effectively, you can write more efficient and flexible Go programs.