Methods as Functions
Learn how Go methods become standalone function values through method values and method expressions, enabling flexible code organisation and composition.
From Methods to Functions
Every Go method is already a function with a special first parameter—the receiver—that the language makes easy to call with dot notation. That familiar value.Method(args) syntax hides the fact that the method is just Type.Method(value, args) underneath. Go exposes this reality in two explicit forms: method values and method expressions. Both let you treat a method like any other function value, but they capture the receiver differently, and that difference determines when you would use one over the other.
A method value binds a specific receiver to a method at the moment you create it. The result is a plain function that you can store, pass, and call without ever mentioning the receiver again. A method expression goes the other direction—it lifts the method off the type entirely and turns the receiver into an ordinary first argument. You call the resulting function with any receiver of that type each time.
If you have ever wanted to pass a method as a callback, store a set of related behaviours in a slice, or delay a method call without holding onto the original variable, these are the mechanisms you reach for.
Receiver semantics carry over:
Everything you know about value receivers and pointer receivers still applies. A method value created from a pointer receiver can mutate the original object; one from a value receiver works on a copy captured at creation time. The same holds for method expressions—the first argument you supply will be treated exactly as the receiver would be.
Method Values
A method value is created by writing receiver.Method without the call parentheses. Go captures the receiver at that moment and produces a function whose signature matches the method’s signature minus the receiver. You can then call that function later just as you would any other function.
How they work
Given a type and a method:
type Counter struct {
count int
}
func (c *Counter) Increment(by int) {
c.count += by
}
You create a method value by selecting the method on a specific instance:
c := &Counter{count: 0}
inc := c.Increment // inc is a func(by int)
The variable inc now holds a function that, when called, will call Increment on the exact *Counter that c pointed to at the moment of assignment. The receiver has been bound permanently.
inc(5)
fmt.Println(c.count) // prints 5
Method values close over the receiver, not the variable. If you reassign c to a new pointer later, the bound function still refers to the original object. With a pointer receiver, both the original variable and the method value share the same underlying memory, so mutations through either path are visible to the other.
Value receiver captures a snapshot:
If Increment had a value receiver (func (c Counter) Increment(by int)), the method value would capture a copy of the Counter at the moment of assignment. Changes made through the method value would affect only that copy, and later calls to c.count would see the original unmodified. This is a frequent source of confusion when refactoring between value and pointer receivers.
Passing method values around
Because a method value is an ordinary function, you can use it anywhere a function is expected.
func applyTwice(f func(int), x int) {
f(x)
f(x)
}
c := &Counter{}
applyTwice(c.Increment, 3)
fmt.Println(c.count) // prints 6
The expression c.Increment evaluates to a func(int) and gets passed directly. The function applyTwice does not need to know about Counter at all. This is what lets you plug methods into higher-order functions, store them in maps, or use them as HTTP handlers without writing wrapper closures.
When a nil receiver becomes dangerous
A method value can be created on a nil pointer receiver if the method handles nil gracefully. If the method dereferences the pointer without checking, the program panics at call time, not at creation time.
var c *Counter // nil
inc := c.Increment // perfectly legal
inc(1) // panic: runtime error: invalid memory address
The creation step succeeds because Go only evaluates the method expression, not the method body. The nil receiver is stored inside the closure, and the panic occurs when inc(1) tries to write to c.count.
Nil receiver panics are deferred:
The binding step never panics. Only the call panics. If you store method values for later execution, always check that the receiver is not nil at the point you call them, or ensure the method itself handles nil receivers explicitly.
Using method values with interfaces
A method value satisfies a function signature, not an interface that requires a method set. However, because you can pass it anywhere a matching func type is expected, it becomes the bridge between concrete type methods and function-typed parameters. If an API asks for a func(int), you hand it counter.Increment. If it asks for an io.Writer, you wrap the method value in an adapter—or you reach for a method expression, which we cover next.
Method Expressions
A method expression takes the method off the type rather than binding it to a single instance. It is written as Type.Method—note the capitalised type name—and produces a function whose first parameter is the receiver.
How they differ from method values
With a method value, the receiver is baked in. With a method expression, the receiver becomes a normal parameter you supply at each call.
type Counter struct {
count int
}
func (c *Counter) Increment(by int) {
c.count += by
}
The method expression (*Counter).Increment yields a function with the signature func(*Counter, int). You call it by passing the receiver explicitly:
c := &Counter{}
f := (*Counter).Increment
f(c, 10)
fmt.Println(c.count) // prints 10
The receiver is no longer special—it is just the first argument. This means you can call the same function with different receivers, or even with a nil receiver if the method allows it.
When to choose a method expression
Method expressions shine when you need to apply the same operation to many different instances, or when the receiver is not known until call time. Consider a slice of *Counter values:
counters := []*Counter{{count: 1}, {count: 2}, {count: 3}}
op := (*Counter).Increment
for _, c := range counters {
op(c, 5)
}
// each counter.count is now 6, 7, 8
A method value would have required you to create a separate function for each element. The method expression gives you a single function and lets you drive the loop yourself.
Another common scenario is when you are writing a function that accepts a method as an argument and you want the caller to supply the receiver.
func executeOn(c *Counter, action func(*Counter, int), amount int) {
action(c, amount)
}
executeOn(&Counter{}, (*Counter).Increment, 42)
The caller passes the method expression and the receiver separately, keeping the decision of which receiver to use at the call site rather than at the point the method expression was created.
Flexible composition:
Method expressions let you treat any method as a plain function whose first argument is the instance. This is how Go achieves polymorphism without inheritance—any type that implements a method can be passed to a function that expects that exact signature. You are composing behaviours from concrete types rather than building deep class hierarchies.
Method expressions and value receivers
The same rules apply with value receivers. Given:
func (c Counter) Value() int {
return c.count
}
The method expression Counter.Value is a func(Counter) int. When called, it receives a copy of the Counter, just as a direct method call would.
v := Counter.Value
fmt.Println(v(Counter{count: 99})) // prints 99
A pointer receiver method expression ((*Counter).Value would not exist in this example) still requires a *Counter as the first argument. Go’s automatic referencing and dereferencing does not apply to method expression calls—you must pass exactly the type that matches the receiver.
No automatic dereferencing in method expressions:
In a regular method call, Go lets you write c.Increment(1) whether c is a Counter or a *Counter, as long as the method set permits it. With method expressions, the compiler enforces the exact receiver type: (*Counter).Increment expects a *Counter, and Counter.Value expects a Counter. Calling (*Counter).Increment with a Counter value (not a pointer) is a compile-time error.
Summary
Method values and method expressions are two sides of the same coin. A method value says, “I already know which instance I want; give me a function I can pass around.” A method expression says, “I have a behaviour that belongs to a type; I’ll tell you which instance to use at the last moment.” The choice between them comes down to when the receiver is determined—at binding time or at call time.
Both forms work because Go methods are ordinary functions that the compiler dresses up with syntactic sugar. Understanding that directly leads to better API design: you can expose a type’s behaviour as function values, build lightweight adapters, and avoid writing boilerplate closures that only exist to capture a receiver.