What does the method expression Point.Add produce in Go, and how does it differ from p.Add?
answer
- type on the left, not a value
- the receiver becomes parameter number one
- nothing is bound, nothing is copied
- pointer form needs parentheses around the star
- Point.Add(a, b) equals a.Add(b)
basics
~20 sPoint.Add is a method expression: it yields an ordinary function whose first parameter is the receiver, so its type is func(Point, Point) Point. Nothing is bound. Writing p.Add instead binds p as the receiver and yields func(Point) Point.
solid answer
~50 sA method expression names the **type** on the left rather than a value, and hands you the method as a plain function with the receiver promoted to an explicit first parameter. For `func (p Point) Add(q Point) Point`, the expression `Point.Add` has type `func(Point, Point) Point`, and you call it as `Point.Add(a, b)`. Nothing is bound and no receiver is copied at the point you write it — the receiver is passed like any other argument, per call. The pointer form is spelled with parentheses: `(*Point).Scale` has type `func(*Point, float64)`. A pointer-receiver method is only reachable through that form, since `Point.Scale` would need a method the value type does not have. Method expressions also work on interface types — `fmt.Stringer.String` is `func(fmt.Stringer) string`, dispatching on whatever dynamic value you pass. In practice you reach for them when a helper wants the receiver as data.
code
go · 11 linestype Point struct{ X, Y int }
func (p Point) Add(q Point) Point {
return Point{p.X + q.X, p.Y + q.Y}
}
func demo() {
add := Point.Add // func(Point, Point) Point
fmt.Println(add(Point{1, 2}, Point{3, 4}))
// prints: {4 6}
}go deeper
Recognise the shape: a capitalised type name before the dot rather than a variable means the receiver is not attached and you must pass it yourself as the first argument.
Be able to write both types out — func(Point, Point) Point versus func(Point) Point — and to explain why the pointer variant is spelled (*Point).Scale with parentheses.
Show judgment about which form belongs in an API: the unbound form keeps the receiver explicit and cannot go stale, the bound form gives callers a plain func they can drop into any callback slot.
Treat it as a readability budget. Method expressions are rare enough that a codebase leaning on them pays a comprehension tax, so decide deliberately where the extra explicitness is worth teaching to everyone who will read the code.
## The unbound form Go has two ways to turn a method into a function value, and they differ in what sits to the left of the dot. - `p.Add` — a **value** on the left. This is a *method value*: the receiver is bound now, and the resulting function does not take a receiver. - `Point.Add` — a **type** on the left. This is a *method expression*: nothing is bound, and the resulting function takes the receiver as its first parameter. For ```go func (p Point) Add(q Point) Point ``` the two expressions have different types: | expression | type | |---|---| | `p.Add` | `func(Point) Point` | | `Point.Add` | `func(Point, Point) Point` | Calling `Point.Add(a, b)` is exactly equivalent to `a.Add(b)`. The compiler generates a small wrapper whose first argument becomes the receiver. ## The pointer form When the method is declared with a pointer receiver you write the type in parentheses: ```go func (p *Point) Scale(f float64) scale := (*Point).Scale // func(*Point, float64) scale(&p, 2) ``` `Point.Scale` is rejected: the value type does not offer a pointer-receiver method for a method expression to name. The reverse pairing is fine — `(*Point).Add` is legal for the value-receiver `Add` and has type `func(*Point, Point) Point`, with the wrapper dereferencing the pointer for you. ## On interface types A method expression may also name an interface type. `fmt.Stringer.String` has type `func(fmt.Stringer) string`; calling it dispatches dynamically on whatever concrete value the argument holds. This is occasionally handy when a generic-ish helper wants "the String method" as data rather than as a call. ## Why it exists The method expression is the form you want when the receiver is one of several things you will supply, rather than one thing you have already chosen. A load generator that runs a mix of named workloads can go either way, and the choice is informative: ```go // Bound: one function per configured runner. mix := map[string]func(){"warmup": runner.Warmup} // Unbound: one function, applied to whichever runner you pass. apply := (*Runner).Warmup for _, r := range runners { apply(r) } ``` The unbound form keeps the receiver in the caller's hands, which sidesteps the whole family of stale-binding questions: there is no saved receiver to go stale, because there is no saved receiver at all. The cost is that every call site must carry the receiver explicitly, and the function's type is noisier. ## What to say in an interview Three sentences cover it. A method expression names a type and yields a function with the receiver as an explicit first parameter. The pointer variant needs the parenthesised `(*T).M` spelling and is the only way to reach a pointer-receiver method this way. It binds nothing, so unlike a method value there is no receiver copy taken at the point the expression is written.
- Why does Point.Scale fail to compile when Scale is declared with a pointer receiver?A method expression can only name a method the given type offers, and a pointer-receiver method is not one of the value type's. Write `(*Point).Scale` instead, which has type `func(*Point, float64)` and takes the pointer explicitly.
- Is a method expression allowed on an interface type?Yes. `fmt.Stringer.String` has type `func(fmt.Stringer) string`, and calling it dispatches on the dynamic value passed as the first argument. The wrapper does the interface call for you, so it behaves exactly like writing `v.String()`.
- When would you prefer the unbound form over a bound method value?When the receiver varies per call — applying one operation across many instances, or writing a helper that is parameterised by the receiver. It also removes any question about stale receivers, since nothing is saved at the point the expression is written.
saying these in an interview costs you the question
- Says Point.Add and p.Add have the same type
- Writes *Point.Scale without the parentheses
- Claims a method expression binds a default receiver
- Thinks Point.Scale works for a pointer-receiver method
- Assumes method expressions are illegal on interface types