Skip to main content
skill CX Codex

go-interface-design — Claude Code skill for Go interfaces

This skill guides Go interface design, package decoupling, and compile-time contract checks. Use it when shaping dependencies or refactoring code for testability.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add eduardo-sl/go-agent-skills --skill go-interface-design -a claude-code
Open on GitHub

What go-interface-design does

The skill explains where an interface belongs, how to keep its contract focused, and when a concrete struct is a better choice. It covers composing interfaces, accepting interfaces while returning structs, and checking implementation at compile time.

It also examines common mistakes, including pointers to interfaces, abstractions introduced too early, and unclear uses of interface{}. Reference material is loaded when needed. The Go toolchain is required; without it, go and gofmt commands cannot run.

This skill focuses on interface design and package boundaries, not HTTP handler patterns or general code review. A project skill or agent rule that explicitly overrides it takes precedence.

What it helps with

  • define an interface in the package that consumes it
  • split a broad contract into smaller interfaces
  • verify a type satisfies an interface at compile time
  • remove a pointer to an interface from a function signature
  • decide whether a single implementation needs an abstraction

Example prompts for the agent

  • Design a small interface for a service that only reads users from storage.
  • Review this broad interface and suggest smaller composed contracts.
  • Add a compile-time check that PostgresStore satisfies the consumer's interface.

How to install go-interface-design

1Skills CLI (Vercel)

npx skills add eduardo-sl/go-agent-skills --skill go-interface-design -a claude-code

2Manually: put the files in place

~/.claude/skills/go-interface-design/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/go-interface-design/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about go-interface-design

How do I install go-interface-design in Claude Code?

Run npx skills add eduardo-sl/go-agent-skills --skill go-interface-design -a claude-code. The skill file is at ~/.claude/skills/go-interface-design/SKILL.md.

What does go-interface-design do?

It helps choose where Go interfaces belong, keep contracts focused, compose smaller interfaces, and check implementations during compilation. It also covers common interface design mistakes.

What do I need to use go-interface-design?

The Go toolchain is required. The skill uses the go and gofmt commands when working with a Go project.

Source text

SKILL.md · MIT
---
name: go-interface-design
description: >
  Go interface design patterns: implicit interfaces, consumer-side
  definition, interface compliance verification, composition, the
  accept-interfaces-return-structs principle, and common pitfalls. Use when
  designing interfaces, decoupling packages, defining contracts, reviewing
  interface usage, or refactoring for testability. Trigger examples: "design
  interface", "accept interfaces return structs", "interface compliance",
  "consumer-side interface", "interface composition".
  Not for: HTTP handler patterns (go-api-design), general review
  (go-code-review).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*)
metadata:
  author: eduardo-sl
  version: "1.2.1"
---

# Go Interface Design

Go interfaces are implicit. This is the single most important design feature
of the language, and most people coming from Java or C# get it wrong at first.

> Community default. A project skill or agent-config rule that explicitly
> supersedes `go-interface-design` takes precedence over this one.

Detailed reference material, loaded on demand:

- `references/embedding-and-assertions.md` — embedding vs named fields,
  interface-embedding test fakes, optional capabilities, sealed interfaces.

Read a reference file only when the summary below is not enough.

## 1. The Cardinal Rule: Define Interfaces at the Consumer

The consumer of a behavior defines the interface, NOT the provider:

```go
// ❌ Wrong — producer defines interface (Java thinking)
// package store
type UserStore interface {      // defined alongside implementation
    GetByID(ctx context.Context, id string) (*User, error)
    Create(ctx context.Context, user *User) error
    // ... 15 more methods
}

type PostgresStore struct { ... }
func (s *PostgresStore) GetByID(...) { ... }
func (s *PostgresStore) Create(...) { ... }

// ✅ Right — consumer defines what it needs
// package service
type UserReader interface {     // only what THIS service needs
    GetByID(ctx context.Context, id string) (*domain.User, error)
}

type UserService struct {
    store UserReader  // depends on narrow interface
}

// package store (no interface defined here)
type PostgresStore struct { db *sql.DB }
func (s *PostgresStore) GetByID(ctx context.Context, id string) (*domain.User, error) { ... }
func (s *PostgresStore) Create(ctx context.Context, user *domain.User) error { ... }

// PostgresStore satisfies service.UserReader implicitly — no declaration needed
```

Why this matters:
- Consumer depends only on what it uses (Interface Segregation Principle).
- Producer can add methods without breaking consumers.
- Testing requires only the methods the consumer calls.
- No import cycle: consumer doesn't import producer's package.

## 2. Keep Interfaces Small

The bigger the interface, the weaker the abstraction.

```go
// ✅ Good — focused, composable
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

// ❌ Bad — kitchen sink interface
type FileManager interface {
    Read(path string) ([]byte, error)
    Write(path string, data []byte) error
    Delete(path string) error
    List(dir string) ([]string, error)
    Move(src, dst string) error
    Copy(src, dst string) error
    Stat(path string) (os.FileInfo, error)
    Watch(path string) (<-chan Event, error)
}
```

Guideline: 1-3 methods is ideal. If you need more, compose smaller interfaces.

## 3. Accept Interfaces, Return Structs

```go
// ✅ Good — accepts interface, returns concrete type
func NewUserService(store UserReader, logger Logger) *UserService {
    return &UserService{store: store, logger: logger}
}

// ❌ Bad — returns interface (hides the concrete type for no reason)
func NewUserService(store UserReader) UserServiceInterface {
    return &UserService{store: store}
}
```

Return a concrete type so callers get full access to the type's methods.
Returning an interface only makes sense when the function genuinely
returns different concrete types based on input (factory pattern).

## 4. Verify Interface Compliance at Compile Time

Use the blank identifier assignment to catch broken contracts early:

```go
// Verify *PostgresStore implements service.UserReader at compile time
var _ service.UserReader = (*PostgresStore)(nil)

// Verify LogHandler implements http.Handler
var _ http.Handler = (*LogHandler)(nil)

// For value receivers:
var _ fmt.Stringer = Status(0)
```

Place these immediately after the type declaration. They cost nothing
at runtime and prevent silent contract breakage.

## 5. Don't Use Pointers to Interfaces

```go
// ❌ Bad — pointer to interface is almost never correct
func process(r *io.Reader) { ... }

// ✅ Good — interface is already a pointer internally
func process(r io.Reader) { ... }
```

An interface value is internally two pointers (type + data).
A pointer to an interface is a pointer to a pointer — needless indirection.

The only exception: when you need to replace the interface value itself
(swap the implementation at runtime), which is extremely rare.

## 6. The Empty Interface

`interface{}` (or `any` in Go 1.18+) means you've given up on type safety.
Use it sparingly:

```go
// ✅ Acceptable — generic container before generics / stdlib compatibility
func Marshal(v any) ([]byte, error)

// ✅ Better (Go 1.18+) — use generics instead of any
func Map[T, U any](slice []T, fn func(T) U) []U { ... }

// ❌ Bad — lazy interface design
func Process(data any) any { ... } // what does this even do?
```

## 7. Optional Configuration Is Not an Interface Problem

Optional constructor settings are not a reason to introduce an interface.
Use functional options or a config struct — the go-design-patterns skill
owns both and the trade-off between them.

## 8. Common Interface Anti-Patterns

### Premature interfaces:

```go
// ❌ Bad — interface defined before second implementation exists
type Processor interface {
    Process(ctx context.Context, data []byte) error
}

type processor struct { ... }  // only one implementation ever

// ✅ Good — use concrete type until you need the abstraction
type Processor struct { ... }
// Add interface when you have 2+ implementations or need testing seam
```

"Don't design with interfaces, discover them." — Rob Pike

### Interface pollution:

```go
// ❌ Bad — wrapping every struct in an interface "for testability"
type UserServiceInterface interface { ... }
type OrderServiceInterface interface { ... }
type PaymentServiceInterface interface { ... }
// 50 more interfaces with exactly one implementation each

// ✅ Good — define interfaces where they're consumed
// Each consumer declares only the methods IT needs
```

### Sealed interfaces vs enums:

A sealed interface — an unexported marker method, so only this package can
add variants — is the right tool for a closed set of variants that carry
different data, consumed with a type switch. When the variants carry no
data, use a typed constant enum instead. Example:
`references/embedding-and-assertions.md`.

## 9. Embedding and Type Assertions

- Embedding promotes the whole method set. Embedding `sync.Mutex` in an
  exported struct publishes `Lock`/`Unlock`; use a named field unless the
  outer type truly *is* the inner one, extended.
- An interface embedded in a struct satisfies it with methods that panic
  when called — fine for test fakes, a hidden runtime failure elsewhere.
- Assert to an interface for optional capabilities
  (`w.(http.Flusher)`), always with comma-ok.
- Generic methods (Go 1.27+) never satisfy an interface method; keep the
  methods of anything you define as an interface non-generic.

## Decision Checklist

1. **Do I need an interface here?** — Only if you have 2+ implementations,
   need a testing seam, or are crossing a package boundary.
2. **Where should it be defined?** — At the consumer, not the producer.
3. **How many methods?** — Fewer is better. 1-3 is ideal.
4. **Am I returning an interface?** — Probably shouldn't. Return concrete.
5. **Have I verified compliance?** — `var _ Interface = (*Type)(nil)`
6. **Am I embedding for convenience?** — named field unless every promoted method belongs on the outer type.

© eduardo-sl · MIT · eduardo-sl/go-agent-skills

Similar skills

All skills for Design →

From the same set34

go-coding-standards This skill enforces idiomatic Go conventions based on Effective Go and Go Code Review Comments. It guides import ordering, struct tags, and early returns when writing or reviewing code. CX Codex +1 ★ 83repo stars go-code-review Review Go diffs, packages, or an entire codebase against a structured checklist. Use it to catch issues before merging changes. CX Codex +1 ★ 83repo stars go-project-layout Picks a folder layout for a new Go project: cmd/, internal/, or a flat root for small tools. Useful at the start, before the structure hardens. CX Codex +1 ★ 83repo stars go-openapi Enforces spec-first REST development in Go by generating servers and clients via oapi-codegen, validating requests, and detecting breaking API changes. CX Codex +1 ★ 83repo stars go-grpc A skill that steers your coding agent through gRPC work in Go: proto contracts, error codes, deadlines, interceptors and streaming. CX Codex +1 ★ 83repo stars go-graphql A skill for building Go GraphQL servers with gqlgen. It guides schema-first generation, fixes N+1 query fan-outs via dataloaders, and enforces query limits. CX Codex +1 ★ 83repo stars go-design-patterns This skill helps an agent choose Go patterns for constructors, runtime behavior and dependency failures. Use it when deciding how to structure a Go project. CX Codex +1 ★ 83repo stars go-dependency-injection A skill that steers your agent toward constructor injection and a single composition root in main. Useful once globals creep in or tests get hard to write. CX Codex +1 ★ 83repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor