Skip to main content
Interfaces are named collections of method signatures. They enable polymorphism in Go — the ability to write code that works with multiple types.

Defining and implementing interfaces

package main

import (
    "fmt"
    "math"
)

// Here's a basic interface for geometric shapes.
type geometry interface {
    area() float64
    perim() float64
}

// For our example we'll implement this interface on rect and circle types.
type rect struct {
    width, height float64
}
type circle struct {
    radius float64
}

// To implement an interface in Go, we just need to implement
// all the methods in the interface. Here we implement geometry on rects.
func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

// The implementation for circles.
func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

// If a variable has an interface type, we can call methods
// that are in the named interface. Here's a generic measure
// function taking advantage of this to work on any geometry.
func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}

    // The circle and rect struct types both implement
    // the geometry interface so we can use instances of
    // these structs as arguments to measure.
    measure(r)
    measure(c)
}

Type assertions

// Sometimes it's useful to know the runtime type of an interface value.
// One option is using a type assertion as shown here.
func detectCircle(g geometry) {
    if c, ok := g.(circle); ok {
        fmt.Println("circle with radius", c.radius)
    }
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}
    
    detectCircle(r)  // Prints nothing (not a circle)
    detectCircle(c)  // Prints: circle with radius 5
}
Type assertions use the syntax value, ok := interfaceVar.(ConcreteType). The ok boolean tells you if the assertion succeeded.

Key concepts

1

Implicit implementation

Interfaces are implemented implicitly. There’s no implements keyword — if a type has all the required methods, it implements the interface automatically.
// rect implements geometry just by having area() and perim() methods
func (r rect) area() float64 { ... }
func (r rect) perim() float64 { ... }
2

Interface types

Variables can be declared with an interface type. They can hold any value that implements that interface.
var g geometry = rect{width: 3, height: 4}
g = circle{radius: 5}  // Also valid
3

Method calls

You can call any method defined in the interface on a variable of that interface type.
func measure(g geometry) {
    g.area()   // Calls the appropriate implementation
    g.perim()  // Based on the runtime type
}

Common interfaces

Go’s standard library defines many useful interfaces:
type Reader interface {
    Read(p []byte) (n int, err error)
}
Used for reading data from files, networks, strings, etc.
type Writer interface {
    Write(p []byte) (n int, err error)
}
Used for writing data to files, networks, buffers, etc.
type Stringer interface {
    String() string
}
Implement this to customize how your type prints with fmt functions.
type error interface {
    Error() string
}
The built-in interface for error values.

Type switches

Another way to handle different types is with a type switch:
func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Integer: %d\n", v)
    case string:
        fmt.Printf("String: %s\n", v)
    case circle:
        fmt.Printf("Circle with radius: %f\n", v.radius)
    default:
        fmt.Printf("Unknown type\n")
    }
}

Interface composition

Interfaces can be composed of other interfaces:
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter combines both interfaces
type ReadWriter interface {
    Reader
    Writer
}

Best practices

Small interfaces

Keep interfaces small. The best interfaces have 1-3 methods.

Accept interfaces

“Accept interfaces, return structs” — functions should accept interface parameters but return concrete types.

Define at use site

Define interfaces where they’re used, not where types are defined.

Empty interface sparingly

Use interface{} (or any) sparingly. Prefer specific types or interfaces.
The empty interface interface{} (or any in Go 1.18+) can hold any value but provides no compile-time type safety. Use it only when necessary.

Methods

Define methods on types

Type assertions

Check concrete types at runtime

Error handling

The error interface