A pointer stores a memory address — the location of a value, not the value itself. Go makes this explicit. You choose, at every point, whether to work with the value or its address. This is similar to C/C++.
☕Java hides this completely. Every object variable is secretly a reference (pointer). You never see the address, you never choose. Go makes the choice visible and explicit — which is why Go code is full of * and &.
The two operators: & and *
& = address-of, * = dereference
x := 42// x lives at some address in memory, say 0xc0000b4008// x holds the value 42p := &x// & means 'give me the address of x'// p is now type *int (pointer to int)// p holds the VALUE 0xc0000b4008 — the address where 42 lives*p = 100// * means 'follow this address and operate on what is there'// This goes to address 0xc0000b4008 and writes 100// That address IS x — so x is now 100fmt.Println(x) // 100 — x changed even though we wrote to pfmt.Println(*p) // 100 — *p reads through the pointerfmt.Println(p) // 0xc0000b4008 — p itself is just an address
Memory model — two variables
x lives at 0xc0000b4008 holds value: 42 p lives at 0xc0000b4016 holds value: 0xc0000b4008 (points to x)
*p = 100 → follow p → go to 0xc0000b4008 → write 100 That location IS x. So x changes.
Java reference vs Go pointer — what is actually different
☕ Java — every object is secretly a reference
// Java hides the pointer — you never chooseMessage m1 = new Message(); // m1 is secretly a referenceMessage m2 = m1; // m2 points to SAME objectm2.retries = 5; // mutates m1.retries too!// Java primitives are copied (not references):int a = 10;int b = a; // b is a copyb = 20; // a is still 10
◎ Go — explicit pointer vs value
// Go — you choose explicitlym1 := Message{Retries: 0} // value on the stackm2 := m1 // m2 is a COPY — independentm2.Retries = 5 // m1.Retries is still 0// To share — use a pointer explicitlym3 := &Message{Retries: 0} // m3 is *Message — a pointerm4 := m3 // m4 points to SAME Messagem4.Retries = 5 // m3.Retries is now 5
Why Go does thisGo gives you control. You choose value (copy, independent) or pointer (shared, mutatable). This makes data flow visible in code. In Java, sharing vs copying is hidden — you have to know the rules for primitives vs objects. In Go, you see it in the type: Message vs *Message.
Auto-dereference for struct fields
go auto-dereferences pointer to struct
type Message struct { Topic string; Retries int }msg := &Message{Topic: "orders"} // msg is *Message// Without auto-deref you would need to write:(*msg).Topic = "updated" // (*msg) dereferences, then .Topic accesses field// But Go auto-inserts (*msg) for struct pointer field access:msg.Topic = "updated" // identical to (*msg).Topic = "updated"msg.Retries++ // identical to (*msg).Retries++// This is why you can call methods on a *Message without writing (*msg).Method():msg.IncrementRetry() // Go inserts the dereference for you
Four reasons you see *Type everywhere in Go code
when to use a pointer
// REASON 1: MUTATION — pointer receiver lets method change the structfunc (m *Message) IncrementRetry() { m.Retries++ // mutates the real Message, not a copy}// REASON 2: AVOID COPYING — large structs are expensive to copy// A *Message is always 8 bytes (64-bit pointer)// A Message with a 1MB Payload would copy 1MB on every callfunc ProcessMessage(msg *Message) error { // pass the address, not the data return nil}// REASON 3: SHARED SINGLETON — one DB pool, one engine instance// Every goroutine uses the same *sql.DB — it manages its own connection poolfunc NewEngine(store MessageStore) *Engine { return &Engine{store: store} // allocate once, share everywhere}// REASON 4: OPTIONAL / NULLABLE — nil pointer means 'not set'type Config struct { MaxRetries *int // nil = use built-in default (3) Timeout *time.Duration // nil = no timeout}
nil pointer — the most common runtime panic
nil pointer dereference — how it happens and how to prevent it
var msg *Message // declared but not initialised — value is nil // msg points to NOTHING// Any field access or method call on nil panics:// msg.Topic PANIC: runtime error: invalid memory address// msg.Retries++ PANIC: same// Always guard before using a pointer you did not create yourself:if msg != nil { fmt.Println(msg.Topic) // safe}// Defensive nil check at function entry — common in engine dispatch:func (e *Engine) Dispatch(msg *Message) error { if msg == nil { return fmt.Errorf("dispatch: received nil message") } return e.store.Save(*msg)}
!Never copy a struct that will contain a mutex or channel (introduced in later sections). Always use a pointer to such structs. go vet detects this.