Back to blog

Sunday, August 16, 2026

Go Method Values vs Method Expressions: What's Actually the Difference?

Title card for 'Go Method Values vs Method Expressions: What's Actually the Difference?' with a short code example

A method value x.M captures x immediately. The result is a function whose signature is the method without the receiver, mux.HandleFunc("/users", srv.ListUsers). A method expression T.M or (*T).M does not bind an instance. The receiver is argument one. Use that when the action is chosen first and the object later.

The failure

You store Counter.Value and call it like a method value:

package main

import "fmt"

type Counter struct{ n int }

func (c Counter) Value() int { return c.n }

func main() {
    c := Counter{n: 5}
    fn := Counter.Value
    fmt.Println(fn()) // compile error: not enough arguments
}

The other direction: fn2 := c.Value then fn2(c) is too many arguments.

Value vs pointer is a second trap. g.Say with a value receiver snapshots g. Change g.prefix later; the stored func still says "hello". A method with a pointer receiver is only reachable through the pointer type, so write (*T).Inc, not T.Inc. T{}.PtrMethod as a method value fails: the composite is not addressable.

Why this happens

Every method is already a function with the receiver as the first parameter. x.M() is T.M(x) (or (*T).M(&x)). The two expressions just choose when that first argument is supplied.

  • Method value: evaluate x now, hide it inside a closure. Callers never pass the receiver. Reassigning the variable x later does not retarget the func; a pointer receiver still sees mutations through the captured address.
  • Method expression: no instance yet. Person.Less is func(Person, Person) bool. A map map[string]func(*TaskRunner) stores (*TaskRunner).Build.

Parentheses matter: (*Counter).Increment, never *Counter.Increment.

The fix

Rule of thumb: if you already have the receiver, use a method value. If the receiver only arrives when you call the function, use a method expression.

type Greeter struct{ prefix string }

func (g Greeter) Say(name string) string {
    return g.prefix + " " + name
}

g := Greeter{prefix: "hello"}
say := g.Say                 // method value: func(string) string
fmt.Println(say("ana"))      // hello ana

fn := Greeter.Say            // method expression: func(Greeter, string) string
fmt.Println(fn(g, "ana"))

actions := map[string]func(*TaskRunner){
    "build": (*TaskRunner).Build,
}
actions["build"](runner)

Need the method to follow a variable that gets reassigned? Write a closure that reads the variable at call time. A method value will not.