Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 18

    Pointers

    Pointers hold memory addresses of values.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Pointers hold memory addresses of values. Go has pointers but no pointer arithmetic — safer than C. Use pointers to mutate function arguments, avoid copying large structs, and represent optional values (nil pointer = absent).

    Understanding & (address-of) and * (dereference), nil pointer checks, and when Go automatically dereferences (p.Field equivalent to (*p).Field) prevents panics and clarifies method receiver choices.

    Interviewers ask pointer vs value semantics, escape analysis hints, and why Go doesn't have pass-by-reference — this lesson answers all with production patterns.

    The story

    A Kubernetes controller passes a pointer to a Deployment spec so the reconciliation loop mutates the object in place before sending a PATCH to the API server. Without pointers, Go copies the entire struct on every function call — expensive for large CRD objects and incorrect when the callee must modify shared state.

    Understanding & (take address) and * (dereference) is essential for JSON unmarshaling into structs, linked lists, and shared counters guarded by mutexes.

    Understanding the topic

    Key concepts

    • &x takes address of x; *p dereferences pointer p.
    • Zero value of pointer is nil — dereferencing nil panics.
    • new(T) allocates zero value on heap, returns *T.
    • Go passes by value — pointer copy shares underlying data.
    • No pointer arithmetic — cannot iterate with ptr++.
    • Optional field: *string nil means absent in JSON with omitempty.

    Step-by-step explanation

    1. Declare: var p *int; p = &value.
    2. Dereference: *p = 42 modifies pointed-to value.
    3. Struct field access auto-dereferences: p.Name works for *Person.
    4. new and & literals allocate; escape analysis may stack-allocate.
    5. Compare pointers with == (same address) or reflect.DeepEqual for content.
    6. Return pointer from function — escapes to heap, GC manages lifetime.

    Practical code example

    Optional JSON field with pointer and in-place mutation via pointer param:

    go
    package main
    import (
    "encoding/json"
    "fmt"
    )
    type Profile struct {
    Name string `json:"name"`
    Nickname *string `json:"nickname,omitempty"`
    }
    func setDefault(n *int, defaultVal int) {
    if *n == 0 {
    *n = defaultVal
    }
    }
    func main() {
    nick := "Al"
    p := Profile{Name: "Alice", Nickname: &nick}
    data, _ := json.Marshal(p)
    fmt.Println(string(data))
    p2 := Profile{Name: "Bob"}
    data2, _ := json.Marshal(p2)
    fmt.Println(string(data2))
    count := 0
    setDefault(&count, 10)
    fmt.Println("count:", count)
    }

    Output

    {"name":"Alice","nickname":"Al"}
    {"name":"Bob"}
    count: 10

    Line-by-line code explanation

    • func Update(d *Deployment) accepts a pointer so mutations affect the caller's struct.
    • d.Replicas = 3 modifies the original — no return value needed.
    • ptr := &deployment takes the address of a variable.
    • value := *ptr dereferences the pointer to read the underlying value.
    • new(int) allocates on the heap and returns *int — rarely needed vs &.
    • nil pointer dereference panics — always validate pointers from external APIs.
    • json.Unmarshal(data, &cfg) requires a pointer so the decoder can populate fields.
    • pointer vs value — small structs (≤64 bytes) may be cheaper by value; large or mutable structs use pointers.

    Key takeaway: *string with omitempty omits nil from JSON. Pointer params enable mutation. Always nil-check before dereference in defensive code.

    Real-world use

    Where you'll use this in production

    • Optional API fields in JSON request/response structs.
    • Mutating function arguments without return values.
    • Linked lists and tree structures in algorithms.
    • Sharing large struct state across goroutines via pointer.

    Best practices

    • Nil-check pointers from external sources before dereference.
    • Use pointers for optional fields in JSON/API structs.
    • Return *T from constructors for consistency with pointer receivers.
    • Don't use pointer to slice/map — they're already reference-like.
    • Avoid unnecessary pointers to small structs — copy cheaper.

    Common mistakes

    • Dereferencing nil pointer — runtime panic.
    • Pointer to loop variable — all pointers same address; use local copy.
    • *&x is just x — redundant indirection.
    • Returning pointer to local variable — safe in Go (escapes to heap).
    • Pointer to interface — almost never needed.

    Advanced interview questions

    Q1BeginnerPass by reference in Go?
    No — always pass by value; pointers copy address enabling shared mutation.
    Q2Beginnernew vs & literal?
    new(T) zero-allocates; &T{v} sets initial value; both return *T.
    Q3IntermediateWhen use pointer to struct?
    Mutation needed, large struct, optional nil semantics, or consistent receiver type.
    Q4IntermediatePointer to slice necessary?
    No — slice header already reference-like; *slice rare and confusing.
    Q5AdvancedExplain nil pointer in interface vs nil interface.
    var p *T=nil; var i interface{}=p → i type *T value nil → i==nil is false.

    Summary

    Pointers enable mutation and optional values; nil means absent. No pointer arithmetic — safer than C/C++. Always nil-check before dereferencing external pointers. Don't pointer-wrap slices and maps unnecessarily. Next lesson: packages — organizing Go code.

    Ready to mark this lesson complete?Track your journey across the entire course.