Advanced

Interfaces

Define behavior contracts that different types can satisfy.

What you will see

woof meow

About 2 min of reading

Try It

You will define an interface, make several types satisfy it implicitly, and inspect values stored in interface variables at runtime.

1

I define an interface with one method and a type whose method matches it. I store the type in the interface variable and call the method through the interface.

package main import "fmt" type Shape interface { Area() float64 } type Circle struct { R float64 } func (c Circle) Area() float64 { return 3.14159 * c.R * c.R } func main() { var s Shape = Circle{R: 2} fmt.Println(s.Area()) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
12.56636
2

I pass an int, a string, and a bool to a function that takes any. The empty interface accepts every value, so each is printed unchanged.

package main import "fmt" func describe(v any) { fmt.Println(v) } func main() { describe(42) describe("hi") describe(true) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
42 hi true
3

I use the two-value type assertion to recover a concrete string and guard it with a boolean so a non-string value is skipped instead of panicking.

package main import "fmt" func show(v any) { s, ok := v.(string) if ok { fmt.Println("string:", s) } } func main() { show("hello") show(42) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
string: hello
4

I define two types that both implement the same interface and store them in a slice of the interface. The loop calls each type's own Area through the interface.

package main import "fmt" type Shape interface { Area() float64 } type Circle struct{ R float64 } func (c Circle) Area() float64 { return 3.14159 * c.R * c.R } type Square struct{ S float64 } func (q Square) Area() float64 { return q.S * q.S } func main() { shapes := []Shape{Circle{R: 1}, Square{S: 3}} for _, s := range shapes { fmt.Println(s.Area()) } }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
3.14159 9
5

I define an interface with a Say method and two types that implement it differently. The loop prints each animal's sound, showing how one interface hides many concrete types.

package main import "fmt" type Sayer interface { Say() string } type Dog struct{} func (Dog) Say() string { return "woof" } type Cat struct{} func (Cat) Say() string { return "meow" } func main() { animals := []Sayer{Dog{}, Cat{}} for _, a := range animals { fmt.Println(a.Say()) } }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
woof meow

Data Flow

A value is stored in an interface; methods are dispatched dynamically at runtime.
1 Assign a concrete value to an interface variable
↓
2 The interface stores type + value
↓
3 Call an interface method
↓
4 The dynamic type's method runs
Why this works
Structural typing

Go interfaces are satisfied implicitly:

type Sayer interface {
    Say() string
}

Any type with a Say() string method is a Sayer, even if it never mentions Sayer. This keeps code decoupled and easy to extend.

Interface composition

Combine interfaces:

type Reader interface {
    Read([]byte) (int, error)
}

type Writable interface {
    Reader
    Write([]byte) (int, error)
}

Writable embeds Reader, so anything that is Writable is also a Reader.

Examples

Defining an interface

Declaring behavior

type Shape interface {
    Area() float64
}

type Circle struct { R float64 }

func (c Circle) Area() float64 {
    return 3.14159 * c.R * c.R
}

An interface is a set of methods. Circle satisfies Shape because it has an Area() method, no explicit declaration needed.

Why it works: Go uses structural typing; a type satisfies an interface by having the methods, not by declaring it.

The empty interface

Any value

func describe(v any) {
    fmt.Println(v)
}

describe(42)
describe("hi")
describe(true)

any (alias for interface{}) accepts any value. It is used in functions like fmt.Println that handle arbitrary types.

Why it works: every type satisfies the empty interface.

Type assertions

Recovering the concrete type

func show(v any) {
    s, ok := v.(string)
    if ok {
        fmt.Println("string:", s)
    }
}

A type assertion v.(string) extracts the concrete value. The two-value form returns an ok boolean instead of panicking.

Why it works: interfaces store both the type and the value.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Structs and Methods
Previous Lesson
Structs and Methods
Next Lesson
Error Handling
Error Handling →