Skip to content

Interfaces

An interface is a type that lists a set of method signatures. Any value whose type has all those methods satisfies the interface — and can be stored in a variable of that interface type. Interfaces are how Go does polymorphism: code depends on what a value can do (its methods), not on its concrete type.

type Shape interface {
    Area() float64
}

Shape is now a type. A variable of type Shape can hold any value that has an Area() float64 method.

Satisfaction is implicit

There is no implements keyword. A type satisfies an interface simply by having the required methods — the compiler checks this structurally. You never declare the relationship; it just exists.

type Rectangle struct{ W, H float64 }
func (r Rectangle) Area() float64 { return r.W * r.H }

type Circle struct{ R float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }

var s Shape = Rectangle{W: 3, H: 4}   // Rectangle satisfies Shape — no declaration needed
fmt.Println(s.Area())                 // output: 12

Both Rectangle and Circle satisfy Shape without ever mentioning it.

From Python: this is duck typing — "if it has the methods, it fits" — but verified at compile time. A type that's missing a method simply won't compile where the interface is expected, instead of blowing up at runtime.

Polymorphism: one function, many types

Because any Shape has Area(), a function can accept the interface and work with every concrete type uniformly:

func totalArea(shapes []Shape) float64 {
    sum := 0.0
    for _, s := range shapes {
        sum += s.Area()
    }
    return sum
}

shapes := []Shape{Rectangle{3, 4}, Circle{1}}
fmt.Printf("%.2f\n", totalArea(shapes))   // output: 15.14

An interface value is a (type, value) pair

Internally an interface value holds two things: the dynamic type of what's stored, and the value itself. The zero value of an interface is nil — no type, no value.

var s Shape          // nil interface
fmt.Println(s == nil)   // output: true

Calling a method on a nil interface panics, because there's no concrete method to dispatch to:

var s Shape
s.Area()   // panic: runtime error: invalid memory address or nil pointer dereference

Once it holds a value, the interface remembers that value's dynamic type — you can see it with %T, the verb that prints a value's type:

var s Shape = Circle{2}
fmt.Printf("%T %v\n", s, s)   // output: main.Circle {2}

Pointer vs value receivers decide satisfaction

This is the most common interface gotcha. A type's method set determines which interfaces it satisfies:

  • methods with value receivers belong to both T and *T
  • methods with pointer receivers belong only to *T

So if a method has a pointer receiver, only a pointer satisfies the interface — a value does not:

type Counter struct{ n int }
func (c *Counter) Add()        { c.n++ }      // pointer receiver
func (c Counter) Value() int   { return c.n }

type Adder interface{ Add() }

var a Adder = &Counter{}   // ok: *Counter has Add
// var a Adder = Counter{} // compile error: Counter does not implement Adder
//                         //   (method Add has pointer receiver)
a.Add()

Rule of thumb: if any method needs a pointer receiver, pass the pointer when you want the value to satisfy an interface.

Asserting satisfaction at compile time

Implicit satisfaction has one cost: nothing in the type's own file says what it is meant to implement, so a mistake like the pointer-receiver one above surfaces at the call site, possibly in another package. The fix is a one-line declaration that asserts it where the type lives:

var _ Adder = (*Counter)(nil)

Read it right to left: (*Counter)(nil) is a nil pointer of a known type, it gets assigned to an Adder, and the result is thrown away with _. Nothing runs and nothing is allocated — the only job is to make the compiler check the method set. The parentheses around *Counter are needed because *Counter(nil) would parse as *(Counter(nil)).

Get it wrong and the build stops here, on the line that states the intent, rather than wherever someone first tried to use the type:

var _ Adder = Counter{}
// compile error: cannot use Counter{} (value of struct type Counter) as Adder
// value in variable declaration: Counter does not implement Adder
// (method Add has pointer receiver)

Both forms are worth knowing. For Circle, whose Area has a value receiver, either one compiles:

var _ Shape = Circle{}         // ok
var _ Shape = (*Circle)(nil)   // also ok — *Circle's method set includes Area

Assert the form callers will actually use. Use it for types whose whole reason to exist is to satisfy an interface: it costs a line and turns a confusing error somewhere else into an obvious one right here.

The empty interface and any

An interface with no methods is satisfied by every type. Its modern spelling is any (an alias for interface{}), and it's how you hold "a value of unknown type."

var x any
x = 42
x = "hello"
fmt.Println(x)   // output: hello

any is a tool of last resort — you lose all compile-time type information. To get the concrete value back out, you use a type assertion or type switch — covered in the next article.

From Python: any is the closest thing to a plain object reference — it can hold anything, and you must check the type before using it specifically.

The typed-nil gotcha

An interface is nil only when both its type and value are nil. If you store a nil pointer in an interface, the interface holds a type, so it is not nil — a frequent source of bugs.

type T struct{}
func (t *T) Foo() {}

var p *T = nil
var i interface{ Foo() } = p
fmt.Println(p == nil)   // output: true
fmt.Println(i == nil)   // output: false  — i has type *T, so it's non-nil

The lesson: return a literal nil for the interface, not a nil concrete pointer, when you mean "nothing."

Small interfaces are idiomatic

Go favours tiny interfaces — often one method — defined where they're used, not where types are defined. The standard library is full of them:

Interface Method Purpose
fmt.Stringer String() string custom text form
error Error() string the error type
io.Reader Read([]byte) (int, error) a source of bytes
io.Writer Write([]byte) (int, error) a sink for bytes
sort.Interface Len/Less/Swap custom sorting

Implement fmt.Stringer and fmt.Println uses it automatically:

type Color struct{ R, G, B int }
func (c Color) String() string {
    return fmt.Sprintf("#%02X%02X%02X", c.R, c.G, c.B)
}
fmt.Println(Color{255, 165, 0})   // output: #FFA500

The guideline: accept interfaces, return concrete types. Take the smallest interface your function actually needs as a parameter, and return the specific struct so callers keep full information.

Quick reference

Concept Syntax
declare an interface type Reader interface { Read(p []byte) (int, error) }
satisfy it just define the methods — no keyword
empty interface any (= interface{}), holds any value
nil interface both type and value nil
assert satisfaction at compile time var _ Adder = (*Counter)(nil)

Extracting the concrete value back out of an interface is covered next, in type assertions and type switches.

Sources