DEV Community

Shivam Jha
Shivam Jha

Posted on

Value Receivers vs Pointer Receivers in Go (A Practical Explanation)

#go

One of the first real design questions you hit in Go is:

Should this method use a value receiver or a pointer receiver?

You’ll see both forms everywhere:

func (u User) Greet() {}
Enter fullscreen mode Exit fullscreen mode

and

func (u *User) UpdateName() {}
Enter fullscreen mode Exit fullscreen mode

They look similar, but the choice matters for correctness, performance, and how your types behave in real backend code.

Let’s break it down properly.


What is a Receiver?

In Go, a method is just a function attached to a type.

type User struct {
    Name string
}
Enter fullscreen mode Exit fullscreen mode

Now we add behavior:

func (u User) Greet() {
    fmt.Println("Hello,", u.Name)
}
Enter fullscreen mode Exit fullscreen mode

The u here is called the receiver. It’s the value the method operates on.

You call it like this:

user := User{Name: "Shivam"}
user.Greet()
Enter fullscreen mode Exit fullscreen mode

So far so good.


Value Receiver: The Method Gets a Copy

When you write:

func (u User) ChangeName() {
    u.Name = "New Name"
}
Enter fullscreen mode Exit fullscreen mode

The method receives a copy of the struct.

That means it can read the data, but if it modifies the receiver, it’s only modifying the copy.

Example:

type User struct {
    Name string
}

func (u User) ChangeName() {
    u.Name = "New Name"
}

func main() {
    user := User{Name: "Shivam"}
    user.ChangeName()

    fmt.Println(user.Name)
}
Enter fullscreen mode Exit fullscreen mode

Output:

Shivam
Enter fullscreen mode Exit fullscreen mode

Nothing changed, because the method was working on a copy.

This is the most important thing to understand:

Value receiver means “work on a copy”.


Pointer Receiver: The Method Works on the Original

Now compare that with:

func (u *User) ChangeName() {
    u.Name = "New Name"
}
Enter fullscreen mode Exit fullscreen mode

Here the receiver is a pointer, so the method has access to the original struct in memory.

Example:

type User struct {
    Name string
}

func (u *User) ChangeName() {
    u.Name = "New Name"
}

func main() {
    user := User{Name: "Shivam"}
    user.ChangeName()

    fmt.Println(user.Name)
}
Enter fullscreen mode Exit fullscreen mode

Output:

New Name
Enter fullscreen mode Exit fullscreen mode

This time the change sticks, because pointer receivers modify the real object.

Pointer receiver means “work on the original”.


When Should You Use Pointer Receivers?

In production Go code, pointer receivers are the default for most structs.

You want pointer receivers when the method needs to modify state. Any method that updates fields should almost always be a pointer receiver.

They’re also important for performance. If your struct is large, using value receivers means copying it every time you call a method. Pointer receivers avoid that copy.

Another big reason is interfaces. In Go, methods with pointer receivers belong only to the pointer type, not the value type. That affects whether your type satisfies an interface, which matters a lot in backend systems.


When Are Value Receivers Fine?

Value receivers are good when the struct is small and the method doesn’t need to modify anything.

A common example is something like a Point type or a read-only helper method.

func (p Point) Distance() float64
Enter fullscreen mode Exit fullscreen mode

If the method is purely about reading data, value receivers are clean and safe.


One Practical Rule That Works Most of the Time

If your type represents something real in a backend system (service, handler, config, DB model, controller), use pointer receivers.

If your type is small and behaves like a simple value, value receivers are fine.

When in doubt, pointer receivers are usually the right choice.


Simply

Value receiver: method works on a copy.
Pointer receiver: method works on the original.

That’s the whole difference, and once you internalize it, Go method design becomes straightforward.


Top comments (0)