No history yet

Introduction to Go Interfaces

Contracts for Behavior

Think about a standard wall outlet. It provides a specific service: delivering electricity. It has a defined shape and voltage. The outlet doesn't care if you plug in a lamp, a phone charger, or a toaster. As long as the device has a compatible plug, it will work. The wall outlet is an interface.

In Go, an interface is a type that specifies a set of behaviors. It's a collection of method signatures that acts as a contract. Any type that fulfills this contract by providing definitions for all the methods in the interface can be treated as that interface type.

An interface is a custom type that specifies a set of method signatures. It defines what a type can do.

Here’s a simple interface that defines a single behavior: the ability to calculate an area.

package main

// Shaper is an interface for types that can calculate their area.
type Shaper interface {
	Area() float64
}

This Shaper interface declares that any type wanting to be considered a Shaper must have a method called Area that takes no arguments and returns a float64.

Implicit Implementation

Unlike in many other programming languages, you don't need to explicitly declare that a type implements an interface in Go. If a type has all the methods an interface requires, Go automatically understands that the type satisfies that interface. This is often called "duck typing": if it walks like a duck and quacks like a duck, it must be a duck.

Let's create two different types, a Rectangle and a Circle.

import "math"

type Rectangle struct {
	Width  float64
	Height float64
}

// Area calculates the area of the rectangle.
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

type Circle struct {
	Radius float64
}

// Area calculates the area of the circle.
func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

Notice that neither Rectangle nor Circle mentions the Shaper interface. But because both types have an Area() float64 method, they both implicitly satisfy the Shaper contract. Go figures this out on its own.

Polymorphism and Decoupling

Interfaces are powerful because they allow for polymorphism, which means "many forms." It lets us write functions that can operate on values of different types, as long as they share the required behavior.

For example, we can write a function that takes any Shaper and prints its area. This function doesn't need to know whether it's dealing with a Rectangle, a Circle, or any other shape.

// PrintArea works with any type that satisfies the Shaper interface.
func PrintArea(s Shaper) {
	fmt.Printf("The area of the shape is %0.2f\n", s.Area())
}

This PrintArea function is decoupled from the concrete types (Rectangle and Circle). It only depends on the Shaper interface. This makes our code more flexible and modular. We could add a Triangle type tomorrow, and as long as it has an Area() method, PrintArea would work with it without any changes.

This decoupling is a core principle of good software design. By depending on abstract interfaces rather than concrete implementations, our code becomes easier to maintain, extend, and test.

We can see this in action by passing different shapes to our function.

func main() {
	r := Rectangle{Width: 10, Height: 5}
	c := Circle{Radius: 4}

	PrintArea(r)
	PrintArea(c)
}

Even though r is a Rectangle and c is a Circle, we can use them both as arguments to PrintArea because they both satisfy the Shaper interface. The function can call s.Area() on whatever shape it receives, trusting that the method exists because of the interface contract.

Quiz Questions 1/5

What is the primary purpose of an interface in Go?

Quiz Questions 2/5

In Go, how does a type explicitly declare that it is implementing a specific interface?