Go Interface Design logo

Go Interface Design

Community
eduardo-sl
go-interface-design

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).

Overview

Publishereduardo-sl
Repositorygo-agent-skills
Skill namego-interface-design
Stars
86
Forks
11
Bundled files
1
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 1 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by eduardo-sl on GitHub. Read the source before you install it.

Installation

Install the Go Interface Design AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/eduardo-sl/go-agent-skills/tree/main/skills/(architecture)/go-interface-design
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.

Use it in TypingMind

Enable Go Interface Design in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Go Interface Design on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Go Interface Design is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Go Interface Design AI skill do?

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).

Why use Go Interface Design on TypingMind?

Because you install it once and use it with any model. Go Interface Design is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Go Interface Design in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/eduardo-sl/go-agent-skills/tree/main/skills/(architecture)/go-interface-design. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Go Interface Design?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Go Interface Design?

As many as you like. As long as a model supports skills, you can use Go Interface Design with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Go Interface Design AI skill free?

Yes. It is published on GitHub by eduardo-sl under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇