Last modified: Jan 23, 2026 By Alexander Williams

Go Pointers with Variables: Simple Guide

Pointers are a core concept in Go. They are powerful but often misunderstood.

This guide will explain pointers in simple terms. You will learn how to use them with variables.

We will cover the basics, syntax, and practical examples. By the end, you will understand pointers clearly.

What is a Pointer in Go?

A pointer is a variable. It stores the memory address of another variable.

Think of it as a direct reference. It points to where the data lives in your computer's memory.

Using pointers allows you to work with the actual data location. This is different from working with a copy of the data.

This is crucial for performance and for modifying data in functions.

Why Use Pointers?

Go is a pass-by-value language. When you pass a variable to a function, Go creates a copy.

This is safe but can be inefficient for large data structures like slices or structs.

Pointers solve this. You pass the memory address instead of the whole value.

This avoids copying large amounts of data. It also lets functions modify the original variable.

Understanding variable scope helps you see why pointers are needed for cross-function modifications.

Declaring and Initializing Pointers

You declare a pointer using the asterisk (*) symbol before a type.

For example, *int is a pointer to an integer.

The ampersand (&) operator gets the address of a variable.


package main

import "fmt"

func main() {
    // Declare a regular integer variable
    myNumber := 42
    fmt.Println("Original value:", myNumber) // Output: 42
    fmt.Println("Memory address of myNumber:", &myNumber)

    // Declare a pointer to an integer
    var ptr *int
    // Initialize the pointer with the address of myNumber
    ptr = &myNumber

    fmt.Println("Pointer value (memory address):", ptr)
    fmt.Println("Value at the pointer address:", *ptr) // Dereferencing
}

Original value: 42
Memory address of myNumber: 0xc0000180a8
Pointer value (memory address): 0xc0000180a8
Value at the pointer address: 42

The output shows ptr holds the same address as &myNumber. The *ptr syntax gets the value stored at that address.

The Zero Value of a Pointer

In Go, an uninitialized pointer has a zero value. This value is nil.

A nil pointer does not point to any memory address.

Dereferencing a nil pointer causes a runtime panic. You must always check if a pointer is nil before using it.


package main

import "fmt"

func main() {
    var nilPtr *string
    fmt.Println("nilPtr is:", nilPtr) // Output: 

    // The next line would cause a panic if uncommented:
    // fmt.Println(*nilPtr) // panic: runtime error: invalid memory address or nil pointer dereference

    // Safe check
    if nilPtr == nil {
        fmt.Println("Pointer is nil, cannot dereference.")
    }
}

Dereferencing: Getting the Value

Dereferencing means accessing the value a pointer points to. Use the asterisk (*) operator on the pointer variable.

This is also called "indirection".


package main

import "fmt"

func main() {
    price := 19.99
    var pricePtr *float64 = &price

    fmt.Println("Price via variable:", price)
    fmt.Println("Price via pointer dereference:", *pricePtr) // Dereferencing

    // You can also modify the value through the pointer
    *pricePtr = 24.99
    fmt.Println("New price via variable:", price) // Output: 24.99
}

Changing *pricePtr changed the original price variable. This is the key power of pointers.

Using Pointers with Functions

This is the most common use case. Pass a pointer to a function to let it modify the original data.

Without a pointer, changes inside a function are lost.


package main

import "fmt"

// Function that tries to modify a value (will NOT work)
func addFiveValue(num int) {
    num = num + 5
    fmt.Println("Inside addFiveValue:", num)
}

// Function that modifies the original value using a pointer (WILL work)
func addFivePointer(num *int) {
    *num = *num + 5 // Dereference, add, assign back
    fmt.Println("Inside addFivePointer:", *num)
}

func main() {
    myValue := 10

    addFiveValue(myValue)
    fmt.Println("After addFiveValue:", myValue) // Still 10!

    addFivePointer(&myValue) // Pass the address
    fmt.Println("After addFivePointer:", myValue) // Now 15
}

Inside addFiveValue: 15
After addFiveValue: 10
Inside addFivePointer: 15
After addFivePointer: 15

The pointer function changed the original myValue. The value function did not.

This concept is vital when working with complex data. For instance, it relates to managing global variables where you want controlled modification.

Pointers with Structs

Pointers are very useful with structs. They prevent copying the entire struct when passing it around.


package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

// This function receives a copy of the struct
func birthdayValue(p Person) {
    p.Age++
    fmt.Println("Inside birthdayValue:", p.Age)
}

// This function receives a pointer to the struct
func birthdayPointer(p *Person) {
    p.Age++ // Go allows this shorthand: no need for (*p).Age
    fmt.Println("Inside birthdayPointer:", p.Age)
}

func main() {
    alice := Person{Name: "Alice", Age: 30}

    birthdayValue(alice)
    fmt.Println("After birthdayValue:", alice.Age) // Still 30

    birthdayPointer(&alice)
    fmt.Println("After birthdayPointer:", alice.Age) // Now 31
}

Notice the shorthand p.Age++ inside birthdayPointer. Go automatically dereferences the pointer for struct fields. This makes code cleaner.

New() Function for Pointer Creation

Go has a built-in new function. It allocates memory for a type and returns a pointer to it.

The allocated memory is zeroed. For an *int, it points to an integer with value 0.


package main

import "fmt"

func main() {
    // Using new()
    ptr := new(int) // ptr is of type *int, points to 0
    fmt.Println("Address from new():", ptr)
    fmt.Println("Initial value from new():", *ptr) // 0

    *ptr = 100
    fmt.Println("Assigned value:", *ptr)

    // Equivalent to:
    var num int
    ptr2 := #
    // But new() is convenient when you don't need a named variable first.
}

new(T) is useful when you just need a pointer to a zero value. It's a clean one-liner.

Common Mistakes and Best Practices

1. Nil Pointer Dereference: Always check if a pointer is nil before using * on it.

2. Unnecessary Pointers: Don't use pointers for small, basic types like int or bool unless you need in-place modification. The copy cost is negligible.

3. Pointer to Pointer: You can have **int, but it's rare and can make code complex. Avoid unless absolutely necessary.

4. Returning Pointers to Local Variables: This is safe in Go. The compiler performs "escape analysis" and allocates such variables on the heap if needed.


package main

import "fmt"

func createPointer() *int {
    v := 255 // Local variable
    return &v // Safe to return address of local variable
}

func main() {
    p := createPointer()
    fmt.Println("Value from returned pointer:", *p) // Works fine
}

Understanding these practices helps you fix common Go variable errors related to pointers.

Conclusion

Pointers in Go are tools for efficiency and control. They let you reference and modify data directly in memory.

Remember the key symbols: & gets an address, * declares a pointer type or dereferences it.

Use pointers with functions to modify arguments. Use them with large structs to avoid copying.

Always be mindful of nil pointers. Start simple and use pointers where they provide clear benefits.

Mastering pointers is a big step towards writing idiomatic and performant Go code. Combine this knowledge with understanding variable declaration to write robust programs.