ProgrammingGo Developer

How do value and pointer receivers work for methods in Go, what are the principles for choosing between them, and what pitfalls are there with related behavior to interfaces?

Pass interviews with Hintsage AI assistant

Answer.

In Go, methods can be declared for both value and pointer types (value/pointer receiver). This feature has been preserved since early versions of the language for explicit control over who will modify the original data. A classic problem is the need for a distinction between the semantics of value (copy, does not modify) and pointer (shared access to data and the ability to modify).

Problem — It is easy to make a mistake by declaring a method with a value receiver and not achieving the expected effect, or by calling a value method on a pointer variable.

Solution — Follow these rules:

  1. Use pointer receiver if the method needs to change the object's state.
  2. Use value receiver for small immutable structures.
  3. For interfaces, pointer receiver is often preferred for consistency.

Example code:

type Counter struct { Value int } func (c Counter) IncCopy() { c.Value++ } // value receiver func (c *Counter) IncPointer() { c.Value++ } // pointer receiver c := Counter{} c.IncCopy() // Value remains 0 c.IncPointer() // Value becomes 1

Key features:

  • Value receiver guarantees a copy of the data and prevents modification from the outside.
  • Pointer receiver allows for changing the internal state of the structure.
  • Interfaces and their implementation depend on the type of receiver, which can lead to surprises during assignment.

Trick questions.

Can you call a value receiver method on a pointer, and a pointer method on a value?

Go "under the hood" automatically dereferences pointers or takes their address, so calling is allowed if the types are compatible. But not always — with interfaces, this does not work as predictably.

var c Counter (&c).IncCopy() // Can call value method via pointer c.IncPointer() // Can call pointer method, Go will automatically take the address

What happens if a structure implements only pointer methods, but it is passed by value to an interface?

Such an object does not implement the interface if it requires pointer methods, so a panic or compilation error may occur.

type D interface { IncPointer() } func f(d D) {} c := Counter{} f(c) // error! Counter by value does not implement the interface f(&c) // correct

Will the structure change when calling a pointer receiver method if a copy of the pointer is passed?

Yes, even when the pointer is copied, the same underlying object is used — the result will be the same.

c := Counter{} p := &c p2 := p p2.IncPointer() // Value will increase

Typical mistakes and anti-patterns

  • Declaring methods with the wrong receiver and trying to change the structure via a copy.
  • Using value receiver for large structures — excessive copying.
  • Interface matching errors due to the receiver.

Real-life example

Negative case

An engineer implements a structure with value receiver methods "Update". The structure is passed through an interface, but changes "disappear" — as they are working with a copy.

Pros:

  • Pure immutability of the structure.

Cons:

  • Expected changes did not occur — hard to trace the bug.

Positive case

Explicit agreement in the team: all methods that change state use only pointer receivers, interfaces are implemented only with pointers, value receivers are for "extensions" and utilities.

Pros:

  • No ambiguity, minimal surprises.

Cons:

  • Sometimes it's difficult to understand the reason for an error when not paying attention to types.