DEV Community

Cover image for Understanding Interfaces in Go — The Complete Beginner’s Guide
Hugo Oliveira
Hugo Oliveira

Posted on

Understanding Interfaces in Go — The Complete Beginner’s Guide

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)
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Define interfaces where they’re used, not where they’re implemented.
    Example: a Mover interface should live in the game logic package, not inside the Player struct package.

  2. 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)
}
Enter fullscreen mode Exit fullscreen mode
  1. Don’t overdesign.
    Avoid creating interfaces “just in case”. Start with concrete types, and extract interfaces later if patterns emerge.

  2. Accept interfaces, return concrete types.
    This makes your functions flexible but still predictable.

func Process(m Mover) { ... }     // flexible input
func NewPlayer() Player { ... }   // clear output
Enter fullscreen mode Exit fullscreen mode

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)