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

Build docs developers (and LLMs) love