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
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) }
{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.07Rect 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:
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()) }
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:
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 }
./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:
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) }
[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:
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) }
(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):
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)) }
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:
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) }
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:
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()) }
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.
main.Square {4} area 16.0