Golang - Interfaces - Best Practices

Publish date: 2025-09-26
Tags: <a href="https://programmercave.com/tags/Go/">Go</a>

Introduction

How you design and use interfaces can have a significant impact on the quality, flexibility, and maintainability of your Go code. The Go community has developed a set of idiomatic best practices for working with interfaces.


1. Prefer Small, Focused Interfaces

The single most important rule of interface design in Go is to keep interfaces small. An interface should typically have only one or two methods. This is the essence of the “I” in the SOLID principles (Interface Segregation Principle).

The io.Reader and io.Writer interfaces are the canonical examples of this. They each have only one method, which makes them incredibly versatile and easy to implement.

Why are small interfaces better?

Example:

// BAD: A large, monolithic interface
type Animal interface {
    Eat()
    Sleep()
    Walk()
    Fly()
    Swim()
}

// GOOD: Small, composable interfaces
type Eater interface {
    Eat()
}
type Walker interface {
    Walk()
}
type Flyer interface {
    Fly()
}

// A function should only ask for what it needs.
// This function doesn't care if the animal can fly or swim.
func takeForAWalk(w Walker) {
    w.Walk()
}

2. “Accept Interfaces, Return Structs”

This is a famous and highly valuable Go proverb.

a) Accept Interfaces

When writing a function, the parameters it accepts should be interfaces whenever possible. This makes the function more general and decoupled from specific implementations.

// This function can open a door from any type that knows how to `Unlock`.
// It doesn't need to know if it's a `CarKey`, a `HouseKey`, or a `DigitalKeycard`.
type Unlocker interface {
    Unlock() error
}

func openDoor(u Unlocker) {
    u.Unlock()
    // ...
}

b) Return Structs

When a function creates and returns a value, it should typically return a concrete type (like a pointer to a struct).

// This function returns a concrete type, not an interface.
func NewCarKey() *CarKey {
    return &CarKey{...}
}

Why?

// The caller gets a concrete *CarKey...
key := NewCarKey()

// ...and can choose to store it in an interface variable if that's all they need.
var unlocker Unlocker = key
openDoor(unlocker)

This pattern provides the maximum flexibility to the consumer of your API.


3. Define Interfaces Where They Are Used

In many object-oriented languages, it’s common to define interfaces in the same package as the types that implement them.

In Go, the idiomatic approach is often to define an interface in the package that uses it, not the package that implements it.

Example: Imagine you have a user package and a storage package. The storage package needs to save users.

Why is this better?

Tags: <a href="https://programmercave.com/tags/Go/">Go</a>