Skip to content

Methods & Interfaces

Most object-oriented languages bolt polymorphism onto a class hierarchy: you declare that a type implements an interface, you extends a base class, and the compiler enforces the lineage. Go throws away the hierarchy entirely.

In Go, any named type can have methods (except pointer types and interface types), and any type that has the right methods automatically satisfies an interface — no declaration required. This is called implicit interface satisfaction, and it is the single most important design decision in the language.

The combination of methods and interfaces gives you:

  • Polymorphism without inheritance trees.
  • Dependency inversion without frameworks — accept an interface, return a struct.
  • Composition without extends — embed types to promote their fields and methods.

Every rule in this module resolves to one of two things at the keyboard: a compile error naming a missing or mismatched method, or a nil surprise inside an interface or an embedded pointer. Learning to recognize both on sight — not just knowing the rule in the abstract — is the actual skill this module builds.

LessonWhat you will learn
MethodsDeclare methods on any named type; value vs pointer receivers; method sets.
InterfacesDefine and satisfy interfaces implicitly; small interfaces; the empty interface any.
Type Assertionsx.(T), the comma-ok form, and switch v := x.(type).
EmbeddingStruct embedding, promoted fields/methods, interface embedding, composition over inheritance.

The snippet below defines a method on a struct, declares a one-method interface, and calls the method through the interface — all in under 20 lines.

package main
import "fmt"
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
type Sizer interface {
Area() float64
}
func printArea(s Sizer) {
fmt.Printf("area = %.2f\n", s.Area())
}
// Counter shows the other half of the story: a pointer receiver that mutates.
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
func main() {
r := Rectangle{Width: 4, Height: 3}
fmt.Println(r.Area())
printArea(r)
c := &Counter{}
c.Inc()
c.Inc()
fmt.Println(c.n) // 2 — pointer receiver mutated the original
}
What you getBenefitCost
Implicit interface satisfactionDecoupled code, no import dependency on the interfaceHarder to discover which types implement which interface
Value receiverCheap to reason about, callable on non-addressable valuesCopies the struct on every call
Pointer receiverCan mutate, no copyNeeds a nil-check, needs an addressable value
Embedding (composition)Reuses methods/fields without inheritingNo virtual dispatch — polymorphism still needs an interface
  • An interface is like an abstract class. In Go an interface has only method signatures — no implementation, no state.
  • Embedding is inheritance. The embedded type has no knowledge of the outer type; there is no override and no virtual dispatch.
  • A nil interface is always == nil. An interface holding a nil pointer is not itself nil — (nil, nil) is not the same as (*T, nil). See Interfaces.

💡 Real-world example

The standard library builds on io.Reader and io.Writer as its core interfaces — os.File, bytes.Buffer, net.Conn, and an HTTP request body all implement the same two methods, so they compose across the whole ecosystem without knowing about each other.

Kubernetes’ runtime.Object interface (k8s.io/apimachinery/pkg/runtime) is satisfied by every Kubernetes API resource — a controller written once against runtime.Object works with Pod, Deployment, and Service alike.

Across every lesson in this module, the same handful of checks catch most generated-code bugs before they ship:

  • Does every method on a type use the same receiver kind? A mix, or a value receiver on a type meant to mutate, is the first thing to check.
  • Is a concrete value assigned to an interface variable when one of the required methods has a pointer receiver? That is a compile error, not a runtime bug — read the message, it names the method.
  • Does a function return a typed nil pointer through an interface-typed result (error, or any other interface)? The caller’s == nil check will be wrong.
  • Does a single-value type assertion (v := x.(T)) run without a comma-ok guard on a value that might not hold T?
  • Does an embedded pointer, or an embedded interface used as a partial mock, get a promoted method called on it before anything assigns it a real value?

🐛 Spot the bug

type Rectangle struct {
Width, Height float64
}
func (r *Rectangle) Area() float64 {
return r.Width * r.Height
}
type Sizer interface {
Area() float64
}
func main() {
var s Sizer = Rectangle{Width: 4, Height: 3}
_ = s
}
Show the answer

Area has a pointer receiver, so Rectangle’s method set (the value type) does not include it — only *Rectangle’s does. Assigning a plain Rectangle{} value to the Sizer interface variable fails to compile: cannot use Rectangle{…} (value of struct type Rectangle) as Sizer value in variable declaration: Rectangle does not implement Sizer (method Area has pointer receiver). The fix is var s Sizer = &Rectangle{Width: 4, Height: 3}.

How does a Go type declare that it satisfies an interface?
Does this compile?
type Rectangle struct{ W, H float64 }
func (r *Rectangle) Area() float64 { return r.W * r.H }
type Sizer interface{ Area() float64 }

var s Sizer = Rectangle{W: 4, H: 3}
What does this print, and why?
type MyErr struct{ msg string }
func (e *MyErr) Error() string { return e.msg }

func f() error {
    var p *MyErr
    return p
}

fmt.Println(f() == nil)
Which of the following can have methods in Go?
What is the idiomatic Go substitute for class inheritance?