# Go Interfaces

> Enforces Go interface design — interfaces defined by the consumer, kept to one or two methods, accepted as parameters while structs are returned, and never created before a second implementation exists. Use when writing or reviewing Go abstractions, mocks, or package boundaries, and when the user mentions interface design, mocking, dependency injection, "accept interfaces return structs", io.Reader, io.Writer, generics vs interfaces, or asks "should this be an interface", "how do I test this dependency".

- Skill: `caslubbers/go-interfaces` (Agent Skill)
- Install (CLI): `npx skillmds@latest add caslubbers/go-interfaces`
- Raw SKILL.md: https://api.skillmd.com/api/skills/caslubbers/go-interfaces/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: CasLubbers (https://skillmd.com/u/caslubbers)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/caslubbers/go-interfaces

---


# Go interfaces

Go interfaces are satisfied implicitly. The implementing type never names the interface, which flips where the interface belongs.

## The consumer declares the interface

Define it in the package that *uses* it, listing only what that package calls.

```go
// package notify — the consumer. It needs exactly one method.
type UserFinder interface {
    FindUser(ctx context.Context, id string) (*User, error)
}

func Send(ctx context.Context, f UserFinder, id string) error { ... }
```

```go
// package store — the producer. Returns a concrete type, declares no interface.
func New(db *sql.DB) *Store { ... }
func (s *Store) FindUser(ctx context.Context, id string) (*User, error) { ... }
```

`*store.Store` satisfies `notify.UserFinder` with no import between them and no declaration linking them. Producer-side interfaces (`store.StoreInterface` next to `store.Store`) invert this: they force every consumer to depend on a surface far wider than it uses, and they change whenever any consumer needs something new.

## Keep them small

One or two methods. The standard library's most reused interfaces have one:

```go
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Stringer interface { String() string }
```

A large interface is not reusable, not implementable in a test without a pile of stubs, and usually a struct wearing a disguise. If yours has seven methods, you have described a type, not a capability.

## Accept interfaces, return structs

```go
// Good — flexible in, concrete out
func NewProcessor(r io.Reader) *Processor
func (p *Processor) Result() *Report
```

Returning an interface hides the concrete type's other methods and fields from callers for no gain, and makes the returned value harder to extend without breaking the interface. Return the struct; let the caller narrow it.

## Reuse the standard interfaces

Before defining anything, check whether `io.Reader`, `io.Writer`, `io.Closer`, `fmt.Stringer`, `error`, `sort.Interface`, or `context.Context` already says it. A function taking `io.Reader` works with files, network connections, `strings.Reader`, `bytes.Buffer`, and gzip streams for free. One taking `*os.File` works with files.

## Wait for the second implementation

An interface with one implementor and no test double is indirection with no seam. Write the concrete type, use it, and extract the interface when the second caller or the first test genuinely needs it — extraction is a two-minute refactor, and by then you know which methods belong.

The exception that earns its keep early: a boundary you cannot run in a test (network, clock, filesystem, payment provider). There, the test double *is* the second implementation.

## Testing without a mocking framework

A hand-written fake is usually shorter than the generated mock and reads better in the failure.

```go
type fakeFinder struct {
    user *User
    err  error
}

func (f fakeFinder) FindUser(context.Context, string) (*User, error) {
    return f.user, f.err
}
```

For a one-method interface, a function type removes even that:

```go
type FinderFunc func(context.Context, string) (*User, error)

func (f FinderFunc) FindUser(ctx context.Context, id string) (*User, error) {
    return f(ctx, id)
}
```

This is how `http.HandlerFunc` works.

## Naming

Single-method interfaces take the method name plus `-er`: `Reader`, `Formatter`, `UserFinder`. No `I` prefix, no `Impl` suffix on the implementation. The concrete type gets the plain noun (`Store`), the interface gets the capability (`UserFinder`).

## Assert satisfaction at compile time

When a type must satisfy an interface it does not mention, state it once so the failure lands at build time with a clear message:

```go
var _ http.Handler = (*Router)(nil)
```

## Empty interfaces and generics

`any` discards all type information and pushes the failure to runtime. If the function is genuinely type-independent, use a type parameter instead:

```go
// Good
func Keys[K comparable, V any](m map[K]V) []K

// Bad — caller must type-assert, compiler cannot help
func Keys(m any) []any
```

Reach for generics when the alternative is the same function copy-pasted per type. Do not parameterise a function that has exactly one instantiation.

