Interfaces

An interface describes behaviour: a set of methods. Any type that has those methods satisfies the interface automatically — there's no implements keyword. Interfaces let you write functions that work with many types, swap real implementations for test doubles, and keep packages loosely connected. This lesson covers declaring and satisfying interfaces, interface values, the empty interface any, and the famous nil-interface trap.

Declaring and satisfying an interface

GO
package main

import (
	"fmt"
	"math"
)

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct{ W, H float64 }

func (r Rect) Area() float64      { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }

type Circle struct{ R float64 }

func (c Circle) Area() float64      { return math.Pi * c.R * c.R }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.R }

func describe(s Shape) {
	fmt.Printf("%-10s area %6.2f  perimeter %6.2f\n", fmt.Sprint(s), s.Area(), s.Perimeter())
}

func main() {
	shapes := []Shape{Rect{3, 4}, Circle{1.5}, Rect{W: 10, H: 0.5}}
	total := 0.0
	for _, s := range shapes {
		describe(s)
		total += s.Area()
	}
	fmt.Printf("total area %.2f\n", total)
}
Output
{3 4}      area  12.00  perimeter  14.00
{1.5}      area   7.07  perimeter   9.42
{10 0.5}   area   5.00  perimeter  21.00
total area 24.07

Rect and Circle never mention Shape. They satisfy it simply because they have both methods with the right signatures. describe works with any shape, including ones written later in other packages.

Interface values

A variable of interface type holds two things: a concrete value and its type. Calling a method calls the concrete type's method. %T shows which type is inside:

GO
package main

import "fmt"

type Speaker interface{ Speak() string }

type Dog struct{}
type Robot struct{ Model string }

func (Dog) Speak() string     { return "Woof" }
func (r Robot) Speak() string { return "Beep, I am " + r.Model }

func main() {
	var s Speaker
	fmt.Println(s == nil)

	s = Dog{}
	fmt.Printf("%T: %s\n", s, s.Speak())

	s = Robot{"RX-8"}
	fmt.Printf("%T: %s\n", s, s.Speak())
}
Output
true
main.Dog: Woof
main.Robot: Beep, I am RX-8

Pointer receivers and interfaces

If a type's methods have pointer receivers, only a pointer to the type satisfies the interface:

GO
package main

type Resetter interface{ Reset() }

type Counter struct{ n int }

func (c *Counter) Reset() { c.n = 0 }

func main() {
	var r Resetter = Counter{} // a Counter value has no Reset method
	_ = r
}
Compiler error
./main.go:10:19: cannot use Counter{} (value of struct type Counter) as Resetter value in variable declaration: Counter does not implement Resetter (method Reset has pointer receiver)

Use &Counter{} instead. This rule exists because a method that modifies its receiver needs the original, not a copy stored inside the interface.

Small interfaces

Go interfaces are usually tiny — often a single method, named after it with an -er suffix: io.Reader, io.Writer, fmt.Stringer, sort.Interface. Small interfaces are easy to satisfy, so more types can work with your code. Bigger ones can be built by embedding:

GO
package main

import "fmt"

type Reader interface{ Read() string }
type Writer interface{ Write(s string) }

type ReadWriter interface { // the union of both
	Reader
	Writer
}

type Buffer struct{ data []string }

func (b *Buffer) Write(s string) { b.data = append(b.data, s) }
func (b *Buffer) Read() string {
	if len(b.data) == 0 {
		return ""
	}
	s := b.data[0]
	b.data = b.data[1:]
	return s
}

func copyAll(dst Writer, src Reader) {
	for s := src.Read(); s != ""; s = src.Read() {
		dst.Write(s)
	}
}

func main() {
	var rw ReadWriter = &Buffer{}
	rw.Write("hello")
	rw.Write("world")

	out := &Buffer{}
	copyAll(out, rw)
	fmt.Println(out.data)
}
Output
[hello world]

A common Go proverb sums up the design advice: accept interfaces, return concrete types. Functions should ask only for the behaviour they need, and constructors should return the real type so callers get all of it.

Interfaces for testing

Because satisfaction is implicit, you can define an interface where you use a dependency and substitute a fake in tests — no mocking framework needed:

GO
package main

import "fmt"

type Notifier interface {
	Send(to, msg string) error
}

type EmailNotifier struct{}

func (EmailNotifier) Send(to, msg string) error {
	fmt.Println("(sending a real email to", to+")")
	return nil
}

type FakeNotifier struct{ Sent []string }

func (f *FakeNotifier) Send(to, msg string) error {
	f.Sent = append(f.Sent, to+": "+msg)
	return nil
}

func remindAll(n Notifier, users []string) {
	for _, u := range users {
		n.Send(u, "your trial ends tomorrow")
	}
}

func main() {
	remindAll(EmailNotifier{}, []string{"ana@example.com"})

	fake := &FakeNotifier{}
	remindAll(fake, []string{"a@test", "b@test"})
	fmt.Println(fake.Sent)
}
Output
(sending a real email to ana@example.com)
[a@test: your trial ends tomorrow b@test: your trial ends tomorrow]

The empty interface: any

An interface with no methods, interface{}, is satisfied by every type. Go spells it any. It's used where values of truly unknown type are needed — fmt.Println's arguments, decoded JSON — but you lose type checking, so to do anything useful with the value you have to find out its type again (next lesson):

GO
package main

import "fmt"

func main() {
	var box any
	box = 42
	fmt.Printf("%v %T\n", box, box)
	box = "now a string"
	fmt.Printf("%v %T\n", box, box)

	mixed := []any{1, "two", 3.0, true, nil}
	fmt.Println(mixed, len(mixed))
}
Output
42 int
now a string string
[1 two 3 true <nil>] 5

Before reaching for any, consider a specific interface, or generics (lesson 47), which keep type safety.

The nil interface trap

An interface value is nil only when both its type and value are unset. An interface holding a nil pointer has a type, so it is not nil. This bites most often with errors:

GO
package main

import "fmt"

type MyErr struct{}

func (*MyErr) Error() string { return "something failed" }

func check(fail bool) error {
	var e *MyErr // nil pointer
	if fail {
		e = &MyErr{}
	}
	return e // wrong: wraps a nil *MyErr in a non-nil error
}

func checkFixed(fail bool) error {
	if fail {
		return &MyErr{}
	}
	return nil // right: return a literal nil
}

func main() {
	err := check(false)
	fmt.Println(err == nil, err != nil)
	fmt.Printf("%T\n", err)
	fmt.Println(checkFixed(false) == nil)
}
Output
false true
*main.MyErr
true

The rule: when a function returns an interface type such as error, return nil itself for "nothing", never a typed nil pointer.

Checking satisfaction at compile time

If you want the compiler to confirm a type satisfies an interface — even before any code uses it that way — add a blank assignment:

GO
package main

import "fmt"

type Shape interface{ Area() float64 }

type Square struct{ Side float64 }

func (s Square) Area() float64 { return s.Side * s.Side }

var _ Shape = Square{} // compile error here if Square stops satisfying Shape

func main() {
	fmt.Println(Square{3}.Area())
}
Output
9

Try it yourself

Add a Triangle type with an Area method and include it in the list. Notice you don't have to change largest at all.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
main.Square {4} area 16.0
Next: finding the concrete type inside an interface.