Skip to content

Go Pointers — & * nil new and Pointer Receivers Explained

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Go Pointers. We cover key concepts, practical examples, and best practices to help you master this topic.

Go pointers hold memory addresses using & to get an address and * to dereference or declare pointer types, with nil representing an uninitialized pointer and pointer receivers for modifying receivers.

What You'll Learn

  • Creating and using pointers
  • The & and * operators
  • nil pointers and safety
  • Pointer receivers in methods

Why It Matters

Pointers enable efficient passing of large structs and modification of values in functions. Docker uses pointers for optional configuration fields. Kubernetes API uses pointers for nullable fields. DodaZIP uses pointers for optional compression parameters.

Real-World Use

A parser function returns a pointer to a parsed struct (or nil on failure). Configuration structs use pointers for optional fields. Large data structures passed as pointers avoid copying.

flowchart LR
    A["Pointers"] --> B["& and *"]
    B --> C["nil"]
    C --> D["Pointer Receivers"]
    D --> E["Methods"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Basic Pointer Operations

package main

import "fmt"

func main() {
    x := 42
    p := &x          // p is a pointer to x
    fmt.Println(p)   // 0xc0000b2008 (memory address)
    fmt.Println(*p)  // 42 (dereference — get value)

    *p = 100         // Change x through pointer
    fmt.Println(x)   // 100
}

Pointer Types

var p *int          // nil pointer to int
var q *string       // nil pointer to string

x := 42
p = &x              // Now p points to x

Pointers in Functions

func zeroValue(x int) {
    x = 0  // Modifies copy
}

func zeroPointer(x *int) {
    *x = 0  // Modifies original
}

func main() {
    n := 10
    zeroValue(n)
    fmt.Println(n)  // 10 (unchanged)

    zeroPointer(&n)
    fmt.Println(n)  // 0 (changed)
}

The new Function

new(T) allocates zeroed memory and returns a pointer:

p := new(int)
fmt.Println(*p)  // 0
*p = 42
fmt.Println(*p)  // 42

// Equivalent to:
var x int
p = &x

nil Pointers

var p *int
fmt.Println(p)      // nil
// fmt.Println(*p)  // Panic! nil pointer dereference

// Safe access
if p != nil {
    fmt.Println(*p)
}

Pointer Receivers

Methods can have pointer receivers to modify the struct:

type Counter struct {
    value int
}

func (c *Counter) Increment() {
    c.value++
}

func (c *Counter) Value() int {
    return c.value
}

func main() {
    c := Counter{}
    c.Increment()
    c.Increment()
    fmt.Println(c.Value())  // 2
}

When to Use Pointer vs Value Receiver

type LargeStruct struct {
    // Many fields...
}

// Pointer receiver — modify struct or avoid copying
func (ls *LargeStruct) Modify() {
    // Can modify fields
}

// Value receiver — read-only, copy is OK for small types
func (ls LargeStruct) ReadOnly() string {
    return "read only"
}

Pointer to Struct

type Person struct {
    Name string
    Age  int
}

func main() {
    // Create pointer to struct
    p := &Person{Name: "Alice", Age: 25}
    fmt.Println(p.Name)  // Go automatically dereferences (p).Name

    // Without & — value
    p2 := Person{Name: "Bob", Age: 30}
    p3 := &p2  // Pointer to p2
    p3.Name = "Charlie"
    fmt.Println(p2.Name)  // Charlie (modified through pointer)
}

Function Arguments

func updateName(p *Person, name string) {
    p.Name = name
}

func main() {
    p := &Person{Name: "Alice"}
    updateName(p, "Bob")
    fmt.Println(p.Name)  // Bob
}

Common Mistakes

1. Nil Pointer Dereference

var p *int
*p = 42  // Panic!

// Always check
if p != nil {
    *p = 42
}

2. Returning Pointer to Local Variable (Actually OK in Go)

func createPerson() *Person {
    p := Person{Name: "Alice"}  // Go allocates on heap if needed
    return &p  // This is safe in Go!
}

3. Using Pointer When Value Would Do

// Unnecessary pointer — small value type
func (c *Counter) Value() *int {
    return &c.value
}

// Better — return value
func (c *Counter) Value() int {
    return c.value
}

4. Confusing * (type) and * (dereference)

var p *int   // *int means "pointer to int"
*p = 42      // *p means "value at pointer p"

5. Not Initializing Maps/Slices in Structs

type Config struct {
    Settings map[string]string
}

c := Config{}
c.Settings["key"] = "value"  // Panic — nil map

Practice Questions

1. What does & do in Go?

& returns the memory address of a variable. It creates a pointer to the variable. x := 5; p := &x makes p point to x.

2. What does * do for pointers?

* has two uses: In a type declaration (*int means "pointer to int"), and as a dereference operator (*p gets the value at the pointer).

3. What is a nil pointer?

A pointer that doesn't point to any memory location. Dereferencing a nil pointer causes a runtime panic.

4. When should you use a pointer receiver vs value receiver?

Use pointer receiver when you need to modify the struct, when the struct is large (avoid copying), or for consistency. Use value receiver for small, immutable types.

Challenge: Write a function that swaps two integer values using pointers.

Solution
package main

import "fmt"

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

func main() {
    x, y := 10, 20
    fmt.Printf("Before: x=%d, y=%d\n", x, y)
    swap(&x, &y)
    fmt.Printf("After:  x=%d, y=%d\n", x, y)
}

Expected output:

Before: x=10, y=20
After:  x=20, y=10

FAQ

{{< faq question="Does Go have pointer arithmetic?" >}} No. Unlike C/C++, Go doesn't allow pointer arithmetic (adding/subtracting from pointers). This prevents a class of memory safety bugs. {{< /faq >}}

{{< faq question="Are maps and slices pointers?" >}} No, but they contain internal pointers to underlying data. This is why maps and slices appear to be reference types when passed to functions. {{< /faq >}}

{{< faq question="What's the difference between new and make?" >} new(T) returns a pointer to a zero-initialized T. make(T, args) initializes slices, maps, and channels (types that need internal setup). new allocates; make initializes. {{< /faq >}}

{{< faq question="Can I return a pointer to a local variable?" >}} Yes. Go's escape analysis determines if a variable escapes the function scope and allocates it on the heap if needed. This is safe and idiomatic. {{< /faq >}}

{{< faq question="Should I use pointers for all function parameters?" >}} No. Use pointers when you need to modify the argument, when the type is large, or when nil is a meaningful value. For small types (int, bool, float64), pass by value. {{< /faq >}}

Try It Yourself

package main

import "fmt"

type Account struct {
    Balance float64
}

func (a *Account) Deposit(amount float64) {
    a.Balance += amount
}

func (a *Account) Withdraw(amount float64) bool {
    if a.Balance < amount {
        return false
    }
    a.Balance -= amount
    return true
}

func main() {
    acc := Account{Balance: 100}
    fmt.Printf("Initial: $%.2f\n", acc.Balance)

    acc.Deposit(50)
    fmt.Printf("After deposit: $%.2f\n", acc.Balance)

    if acc.Withdraw(30) {
        fmt.Printf("After withdrawal: $%.2f\n", acc.Balance)
    }

    fmt.Printf("Pointer: %p\n", &acc)
    fmt.Printf("Balance via pointer: $%.2f\n", (&acc).Balance)
}

Expected output:

Initial: $100.00
After deposit: $150.00
After withdrawal: $120.00
Pointer: 0xc0000b2008
Balance via pointer: $120.00

What's Next

Now that you understand pointers, learn about methods with value and pointer receivers.

Topic Description Link
Go Methods Receiver types, value vs pointer {{< ref "10-methods" >}}
Go Interfaces Implicit satisfaction, type assertion {{< ref "11-interfaces" >}}
Rust Ownership Compare Rust Ownership model Rust

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go