Interfaces

An interface in Go lists a set of methods a type must have — but unlike Java or C#, a Go type never declares which interfaces it implements. If it has the right methods, it satisfies the interface automatically.

Defining and implicitly satisfying an interface

Go main.go
package main

import "fmt"

type Shape interface {
	Area() float64
}

type Rectangle struct {
	Width, Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func describe(s Shape) {
	fmt.Println("Area:", s.Area())
}

func main() {
	rect := Rectangle{Width: 3, Height: 4}
	describe(rect)
}
Output
Area: 12

Shape is an interface requiring one method, Area() float64. Rectangle never mentions Shape anywhere in its own definition — but because it has an Area() method with a matching signature, it satisfies Shape automatically, so describe(rect) compiles and works.

This is sometimes called structural typing, or "duck typing, but checked at compile time": if it walks like a Shape and quacks like a Shape, Go treats it as one, no explicit declaration required.

Multiple types, one interface

Go main.go
package main

import "fmt"

type Shape interface {
	Area() float64
}

type Rectangle struct {
	Width, Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

type Circle struct {
	Radius float64
}

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

func main() {
	shapes := []Shape{
		Rectangle{Width: 3, Height: 4},
		Circle{Radius: 2},
	}

	for _, s := range shapes {
		fmt.Println(s.Area())
	}
}
Output
12
12.56636

A single slice of type []Shape holds both a Rectangle and a Circle without any shared parent type or explicit "implements Shape" declaration — this is Go's version of polymorphism, built entirely on matching method sets.

The empty interface

interface{} (or its modern alias, any) has zero required methods, so literally every type satisfies it — useful when you genuinely don't know or care about a value's type ahead of time:

Go main.go
package main

import "fmt"

func describe(v any) {
	fmt.Printf("%v is a %T\n", v, v)
}

func main() {
	describe(42)
	describe("hello")
	describe(true)
}
Output
42 is a int
hello is a string
true is a bool
Note: reach for any sparingly — accepting it gives up the compile-time type checking that's one of Go's biggest strengths, pushing the work to runtime type assertions instead. It's the right tool for something like a generic logging function, and the wrong tool for most everyday code, where a specific interface (like Shape above) documents exactly what's expected far better than "could be absolutely anything."