LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2719 min read

Interfaces

Learn Go's implicit interface satisfaction, the empty interface "any", and why this structural approach makes Go interfaces distinctive.

Introduction

Interfaces are how Go achieves polymorphism — writing code that works with any type that supports a particular set of behaviors, without caring about the concrete type underneath. What makes Go's interfaces distinctive is that satisfaction is implicit: there's no "implements" keyword. If a type has the right methods, it automatically satisfies the interface.

What You Will Learn
  • How to define an interface type.
  • What implicit interface satisfaction means and why it matters.
  • How to use an interface as a function parameter or variable type.
  • What the empty interface (any) represents.
  • Why Go's structural typing approach differs from languages with explicit "implements" declarations.

Defining an Interface

An interface type is a set of method signatures. Any type that defines all of those methods, with matching signatures, satisfies the interface — regardless of where that type is defined.

package main
type Shape interface {
Area() float64
Perimeter() float64
}

This says: "Anything with an Area() float64 method and a Perimeter() float64 method counts as a Shape." No struct needs to mention Shape by name at all.

Implicit Satisfaction

Unlike Java's "class Circle implements Shape" or C#'s equivalent, Go types never declare which interfaces they satisfy. Satisfaction is determined purely by whether the methods exist — this is sometimes called "structural typing" or "duck typing with compile-time checks."

package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
func describe(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
c := Circle{Radius: 4}
r := Rectangle{Width: 3, Height: 5}
// Neither Circle nor Rectangle mentions "Shape" anywhere,
// but both satisfy it automatically.
describe(c)
describe(r)
}
Output

Click Run to see what this code prints.

Using an Interface as a Type

Interfaces can be used anywhere a type is expected: function parameters, return types, struct fields, or slice element types. A variable of interface type can hold any concrete value that satisfies it, and you can build heterogeneous collections this way.

package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
type Square struct{ Side float64 }
func (s Square) Area() float64 { return s.Side * s.Side }
func main() {
shapes := []Shape{
Circle{Radius: 2},
Square{Side: 3},
}
total := 0.0
for _, s := range shapes {
total += s.Area()
}
fmt.Printf("Total area: %.2f\n", total)
}
Output

Click Run to see what this code prints.

The Empty Interface: any

An interface with zero methods is satisfied by every type in Go, since every type trivially has "at least zero methods." This is written as interface{}, or more commonly today, using the built-in alias any (introduced in Go 1.18). It's useful when you genuinely need to accept a value of any type, though it should be used sparingly since it sacrifices compile-time type safety.

package main
import "fmt"
func describeAnything(v any) {
fmt.Printf("value: %v, type: %T\n", v, v)
}
func main() {
describeAnything(42)
describeAnything("hello")
describeAnything(3.14)
describeAnything(true)
}
Output

Click Run to see what this code prints.

Use any Sparingly

Accepting any means you lose compile-time type checking and typically need a type switch or type assertion to do anything useful with the value. Prefer specific interfaces or generics (covered in advanced material) whenever possible.

Why This Makes Go Distinctive

Because satisfaction is implicit, you can define a small interface after the fact, in your own package, that existing types — even ones from the standard library or third-party packages you don't control — automatically satisfy, as long as they already have the right methods. This encourages a style of small, focused interfaces defined at the point of use, rather than large interfaces defined alongside their implementations. The standard library's io.Reader and io.Writer, each with a single method, are the canonical examples of this philosophy.

package main
import "fmt"
// Stringer is a tiny interface, defined right here.
type Stringer interface {
String() string
}
type Temperature float64
// Temperature never mentions Stringer, yet it satisfies it.
func (t Temperature) String() string {
return fmt.Sprintf("%.1f°C", float64(t))
}
func printIt(s Stringer) {
fmt.Println(s.String())
}
func main() {
printIt(Temperature(23.5))
}
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Defining large interfaces with many methods up front — idiomatic Go favors small interfaces with one to three methods.
  • Reaching for any (or interface{}) as a default, instead of a specific interface or concrete type, losing type safety unnecessarily.
  • Forgetting that method sets differ between value and pointer receivers — a type with only pointer-receiver methods may not satisfy an interface when used by value.
  • Defining an interface in the same package as its only implementation, when Go's convention is often to define interfaces where they're consumed.
  • Assuming Go interfaces need an explicit declaration of intent — there is no "implements" keyword to forget.

Best Practices

  • Keep interfaces small — one or two methods is the sweet spot for maximum reusability.
  • Define interfaces in the package that consumes them, not necessarily alongside the concrete types that implement them.
  • Use any only when you genuinely need to accept arbitrary types, and pair it with a type switch to recover type safety inside the function.
  • Name single-method interfaces after the method with an "-er" suffix, following the standard library convention (Stringer, Reader, Writer).
  • Let interface satisfaction happen naturally — don't force a type to "declare" anything just to satisfy an interface.

Frequently Asked Questions

Check whether it defines every method in the interface with a matching signature. The Go compiler will tell you immediately if you try to use a type where an interface is expected but a method is missing.

They are exactly the same thing — any is simply a built-in alias for interface{}, introduced in Go 1.18 to make empty-interface code more readable.

No, interfaces only define method signatures — they cannot have fields or any implementation of their own.

Yes — an interface can embed other interfaces, combining their method sets into a larger interface. This is a form of composition, similar to what you'll see with struct embedding in the next lesson.

Key Takeaways

  • An interface defines a set of method signatures that any type can implicitly satisfy.
  • Go has no "implements" keyword — satisfaction is purely structural, based on matching methods.
  • Interfaces can be used as parameter types, return types, and collection element types.
  • The empty interface any is satisfied by every type, but sacrifices compile-time type safety.
  • Idiomatic Go favors small, focused interfaces defined at the point of use.

Summary

Go's implicit interface satisfaction is one of its most distinctive design choices, enabling decoupled, flexible code without the ceremony of explicit "implements" declarations found in many other languages. Next, you'll learn about struct embedding — Go's alternative to inheritance, and how it composes naturally with the interfaces you just learned.

Next Lesson →

Embedding & Composition