One of the most misunderstood features for newcomers to Go is the interface.
But once you understand how they really work, you’ll realize that they are one of Go’s most powerful design tools.
Let’s explore what they are, how they work, and why Go’s approach is so unique.
What is an Interface?
In Go, an interface defines behavior, not data.
It’s simply a collection of method signatures — no implementation, no data fields.
Think of it as a contract:
If a type has all the methods that an interface declares, it implicitly implements that interface.
“If it walks like a duck and quacks like a duck, it’s a duck.”
That’s the Go philosophy.
Example:
type Mover interface {
Move(int, int)
}
Any type that has a Move(int, int) method automatically implements Mover.
No implements keyword, no inheritance needed. Go keeps it simple.
Working Example
Let’s build something fun: a small game-like example using structs, methods, and interfaces.
Structs: Our data types
type Item struct {
X int
Y int
}
type Player struct {
name string
Item // Embedded struct (composition)
Keys []string // Keys collected by the player
}
Item represents a position in a 2D space,
and Player embeds an Item, gaining its fields and methods (composition instead of inheritance).
Adding Methods
We’ll give Item a Move method:
func (i *Item) Move(deltaX, deltaY int) {
i.X += deltaX
i.Y += deltaY
}
Notice the receiver:
(i *Item) means this method belongs to Item and can modify it (pointer receiver).
Now both Item and Player can move, since Player embeds Item.
Implementing the Interface
Remember our interface?
type Mover interface {
Move(int, int)
}
Both *Item and *Player have a Move method.
Therefore, they automatically implement Mover.
Let’s use that to our advantage:
func moveAll(ms []Mover, dx, dy int) {
for _, m := range ms {
m.Move(dx, dy)
}
}
Now moveAll can move anything that can “move” — whether it’s an Item, a Player, or any other type you create later.
i := Item{X: 1, Y: 2}
p := Player{name: "Hugo", Item: Item{X: 10, Y: 20}}
ms := []Mover{&i, &p}
moveAll(ms, 5, 5)
fmt.Printf("%+v\n%+v\n", i, p)
This is polymorphism in Go — simple, explicit, and fast.
Composition Over Inheritance
Go doesn’t have inheritance like Java or C#.
Instead, it promotes composition — combining small, focused types together.
When you embed one struct inside another, like this:
type Player struct {
Item
}
You get field and method promotion:
Player now “has” all the fields and methods of Item.
This approach is cleaner, more flexible, and avoids complex class hierarchies.
Best Practices for Interfaces
Define interfaces where they’re used, not where they’re implemented.
Example: aMoverinterface should live in the game logic package, not inside thePlayerstruct package.Keep interfaces small.
The Go standard library averages only 2 methods per interface.
Small interfaces are easier to implement and compose.
type Reader interface {
Read([]byte) (int, error)
}
Don’t overdesign.
Avoid creating interfaces “just in case”. Start with concrete types, and extract interfaces later if patterns emerge.Accept interfaces, return concrete types.
This makes your functions flexible but still predictable.
func Process(m Mover) { ... } // flexible input
func NewPlayer() Player { ... } // clear output
When to Use Interfaces
Use them when you need:
- Polymorphism (different types, same behavior)
- Mocks for testing
- Abstraction between packages (reduce coupling)
- Multiple implementations of a common behavior
Don’t use them when:
- You only have one implementation
- You don’t need to abstract the behavior
- You’re just defining data, not behavior
Recap
| Concept | Description |
|---|---|
| Interface | A set of method signatures that define behavior |
| Implicit implementation | If a type has all required methods, it automatically implements the interface |
| Polymorphism | Different types can be used interchangeably if they satisfy the same interface |
| Composition | Embedding structs to reuse behavior (Go’s alternative to inheritance) |
| Best practice | Keep interfaces small and define them where they’re used |
Interfaces in Go are a perfect reflection of the language’s philosophy:
simple, explicit, and pragmatic.
They let you focus on what really matters — behavior — without the complexity of inheritance chains or abstract hierarchies.
Once you understand that interfaces are about what something can do, not what it is, you’ll start writing idiomatic Go code that feels natural and elegant.
Top comments (0)