---
title: "09 — Pointers and Methods — Receivers, Value vs Pointer, and GC"
uid: pointers-and-methods
tags: ["memory-management", "pointer-receivers", "golang", "methods", "value-receivers", "roadmap:golang", "garbage-collection", "fundamentals", "pointers"]
excerpt: "A pointer is a safe address holder with no arithmetic; a method is a function with a receiver. One dial runs through both: value receivers copy, pointer receivers alias."
date: 2026-08-13T03:28:11+0000
source: https://www.aveshina.my.id/en/blog/pointers-and-methods
---

Pointers and methods bled into each other until I treated them as one topic — which is what they are. The model that clicked: **a pointer is a memory address with no arithmetic, a method is just a function with a receiver argument, and the one decision that runs through both is value-versus-pointer: value receivers copy and cannot mutate, pointer receivers alias and can.** [1][2] Go's memory story — automatic garbage collection, escape analysis choosing stack vs heap — sits underneath and explains when pointers cost anything at all. Once the value/pointer receiver choice felt like a single dial, methods stopped being a separate concept from functions.

## Pointers — addresses without the footguns

A pointer holds the memory address of another variable. Declare with *Type, take an address with &, dereference with * [1]:

```
x := 42
p := &x          // p is *int, points at x
fmt.Println(*p)  // 42 — read through the pointer
*p = 99          // write through the pointer
fmt.Println(x)   // 99 — x is changed
```

Two things make Go pointers safer than C's. There is **no pointer arithmetic** — you cannot do p++ to walk to the next memory cell, which removes an entire category of buffer-overrun bugs. And there is no manual free — the garbage collector reclaims memory once nothing references it, so dangling-pointer bugs essentially do not exist [3]. The result is a pointer that gives you the two things you actually need (pass-by-reference semantics and avoiding large copies) without the sharp edges.

Pointers to structs have one ergonomic shortcut: p.Field works directly and means the same as (*p).Field [4]. You almost never write the explicit dereference for struct field access.

## Pointers with maps and slices — already references

The subtlety that confused me: slices and maps do not usually _need_ a pointer because they are reference-like types already [5]. Passing a slice to a function copies the slice header, and both headers share the underlying array — so modifying an existing element is visible to the caller without any pointer. The only time you need a *[]T or *map[K]V is when the function must reassign the variable itself (e.g., call append in a way the caller sees, or replace the map). For ordinary read/write of shared contents, the bare slice or map is enough.

## Methods — functions with a receiver

A method is a function declared with a **receiver** argument, which attaches it to a type [6]:

```
type Rectangle struct{ Width, Height float64 }

// Value receiver — operates on a copy
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

r := Rectangle{Width: 3, Height: 4}
fmt.Println(r.Area())   // 12
```

The (r Rectangle) between func and the method name is the receiver — it plays the role of this or self in other languages, but it is an explicit named parameter. Methods can be defined on any type in the same package, not just structs, which is how you add behavior to type MySlice []int or type Celsius float64.

## Pointer receivers vs value receivers

The receiver can be a value or a pointer, and the choice is the most consequential method decision [2][7]:

```
// Pointer receiver — receives *Rectangle, can mutate the original
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// Value receiver — receives a copy, cannot mutate the caller's struct
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}
```

The rule I use, drawn from the standard library's own conventions:

- **Use a pointer receiver** when the method **mutates** the receiver, when the struct is **large** (copying is wasteful), or for **consistency** once any method on the type has a pointer receiver.
- **Use a value receiver** when the method only reads, the type is small, and you want the method callable on both values and pointers.

The consistency point matters: a type should generally pick one receiver kind and stick with it, because value and pointer receivers satisfy interfaces differently (a value can only satisfy an interface with value methods; a pointer can satisfy both). Mixing them across a type creates confusion about which set of methods exists on T versus *T.

Go smooths the call site: r.Scale(2) works whether r is a value (Go auto-takes the address) or a pointer (Go auto-dereferences), so the caller does not care. The decision lives in the method declaration, not the call.

## Memory management and garbage collection

Underneath all of this, Go manages memory automatically. The runtime decides, via **escape analysis**, whether each variable is allocated on the stack (fast, freed automatically when the function returns) or the heap (slower, reclaimed by the garbage collector) [8]. The rule of thumb: if a variable's address escapes the function — returned as a pointer, stored in a global, captured by a goroutine — it goes on the heap; otherwise it stays on the stack. You can ask the compiler to report its decisions with go build -gcflags="-m".

The garbage collector is a **concurrent, tri-color mark-and-sweep** collector tuned for low pause times [9]. It runs alongside your program, not in a stop-the-world burst, which is why Go services can hit sub-millisecond GC pauses in practice. The practical implication: you rarely think about deallocation, but you _do_ think about allocation rate, because every heap allocation is eventual GC work. Reducing allocations (pre-sized slices, object pooling with sync.Pool, value receivers for small structs) directly reduces GC pressure. This is the one place where "Go has a GC" stops being a free lunch and starts being a performance consideration.

## How I use this

The value-vs-pointer decision is the lever I pull most. For any method that mutates its receiver, I reach for a pointer receiver without thinking — there is no debate. For read-only methods on small structs, a value receiver keeps things simple and lets the type satisfy interfaces from both values and pointers. The consistency rule then propagates: if one method needs a pointer, the rest get pointers too, so the set of methods is uniform. On the memory side, I run go build -gcflags="-m" when a hot path allocates more than I expect, and the escape-analysis output tells me exactly which local variables are fleeing to the heap. The picture — pointer-as-safe-address, method-as-function-with-receiver, GC-as-concurrent-sweeper — is what makes the tradeoffs visible.

## References

[1] The Go Authors, "Go Tour: Pointers," go.dev, 2024. [Online]. Available: [https://go.dev/tour/moretypes/1](https://go.dev/tour/moretypes/1)

[2] The Bug Shots, "Understanding Value and Pointer Receivers in Golang," Medium, 2024. [Online]. Available: [https://medium.com/the-bug-shots/understanding-value-and-pointer-receivers-in-golang-82dd73a3eef9](https://medium.com/the-bug-shots/understanding-value-and-pointer-receivers-in-golang-82dd73a3eef9)

[3] SafetyCulture Engineering, "An overview of memory management in Go," Medium, 2024. [Online]. Available: [https://medium.com/safetycultureengineering/an-overview-of-memory-management-in-go-9a72ec7c76a8](https://medium.com/safetycultureengineering/an-overview-of-memory-management-in-go-9a72ec7c76a8)

[4] The Go Authors, "Go Tour: Pointers to structs," go.dev, 2024. [Online]. Available: [https://go.dev/tour/moretypes/4](https://go.dev/tour/moretypes/4)

[5] The Go Authors, "Go Maps in Action," The Go Blog, 2013. [Online]. Available: [https://go.dev/blog/maps](https://go.dev/blog/maps)

[6] The Go Authors, "Go Tour: Methods," go.dev, 2024. [Online]. Available: [https://go.dev/tour/methods/1](https://go.dev/tour/methods/1)

[7] Stackademic, "Go Method Receivers: Understanding Value vs. Pointer and When to Use Each," Medium, 2024. [Online]. Available: [https://blog.stackademic.com/go-method-receivers-understanding-value-vs-pointer-and-when-to-use-each-74ef82d66a5c](https://blog.stackademic.com/go-method-receivers-understanding-value-vs-pointer-and-when-to-use-each-74ef82d66a5c)

[8] S. Narayan, "How Go Manages Memory and Why It's So Efficient," Medium, 2024. [Online]. Available: [https://medium.com/@siddharthnarayan/how-go-manages-memory-and-why-its-so-efficient-68c13133ba1c](https://medium.com/@siddharthnarayan/how-go-manages-memory-and-why-its-so-efficient-68c13133ba1c)

[9] The Go Authors, "A Guide to the Go Garbage Collector," go.dev, 2024. [Online]. Available: [https://tip.golang.org/doc/gc-guide](https://tip.golang.org/doc/gc-guide)

```quiz
Q: Why does Go forbid pointer arithmetic?
- The compiler cannot generate the instructions
- To eliminate an entire class of buffer-overrun and memory-safety bugs at the language level
correct: 1
explain: Dropping arithmetic keeps pointers as safe references. You get pass-by-reference and cheap large-struct passing without the memory-corruption footguns that arithmetic creates in C.

Q: When should a method use a pointer receiver?
- Whenever the method must mutate the receiver, the struct is large, or other methods on the type already use pointer receivers
- Always — value receivers are deprecated
correct: 0
explain: Pointer receivers are for mutation, large structs, and consistency. Small read-only types can use value receivers, and the convention is to pick one kind per type and stick with it.

Q: A pointer receiver method can be called on a plain value because…
- it cannot — you must take the address manually at every call site
- Go automatically takes the address of the value for you
correct: 1
explain: The call site is the same whether the receiver is a value or pointer. If the method has a pointer receiver and you call on an addressable value, Go inserts the address-of for you.

Q: What does escape analysis decide?
- whether a variable is a pointer or a value
- whether each variable is allocated on the stack (fast, auto-freed) or the heap (GC-managed)
correct: 1
explain: Escape analysis tracks whether a variable's address escapes its function. If it does, the variable must live on the heap so it survives the return; otherwise it stays on the stack. View decisions with go build -gcflags="-m".

Q: Why does allocation rate matter for performance in a garbage-collected language?
- It does not — the GC handles everything for free
- Every heap allocation is future GC work, so reducing allocations reduces GC pauses and CPU spent collecting
correct: 1
explain: The GC's cost scales with the amount of live and dead heap. Fewer allocations (pre-sized slices, sync.Pool, value receivers for small types) mean less work for the collector and lower pause times.
```
