Skip to content
Development
Skill

/go-interfaces

Use when defining or implementing Go interfaces, composing types through embedding, designing dependency-injection seams, or deciding between pointer and value receivers. Apply proactively whenever a new abstraction is introduced or a constructor returns an abstract type, even

From plugin
gophers
926 skills4 agents

Claude Code

$ npx -y skills add muratmirgun/gophers --skill go-interfaces --agent claude-code

SKILL.md

go-interfaces.SKILL.md
name: go-interfaces
description: "Use when defining or implementing Go interfaces, composing types through embedding, designing dependency-injection seams, or deciding between pointer and value receivers. Apply proactively whenever a new abstraction is introduced or a constructor returns an abstract type, even if the user has not asked about interfaces. Does not cover generics (see go-generics)."
license: MIT
compatibility: "Designed for Claude Code or similar AI coding agents. Targets Go 1.21+. Generics guidance is delegated to go-generics."
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)

Go Interfaces and Composition

Interfaces in Go are *consumer contracts*, not implementation hierarchies. They should be **small**, **discovered late**, and **owned by the package that uses them** — not the package that satisfies them.

Core Rules

1. **Accept interfaces, return concrete types.** Consumers state what they need; producers expose what they have. 2. **Interfaces belong in the consumer package.** Defining an interface next to its sole implementation is almost always wrong. 3. **Don't design with interfaces — discover them.** Wait for a second implementation or a test mock to demand one. 4. **The bigger the interface, the weaker the abstraction.** Aim for 1–3 methods; compose larger contracts from smaller ones. 5. **Receiver consistency:** if any method needs a pointer receiver, give *every* method a pointer receiver. 6. **Verify satisfaction at compile time** with `var _ I = (*T)(nil)` when the relationship must not break silently. 7. **Use the comma-ok idiom for every type assertion.** A bare assertion panics on mismatch.

Decision: Should I Introduce an Interface?

| Situation | Verdict | |---|---| | Single implementation, no tests need to swap it | No interface. Use the concrete type. | | Second implementation appears (or is imminent) | Extract an interface in the consumer package. | | Test needs to fake an external dependency | Define a small interface in the consumer; pass a fake. | | You want to expose optional behaviour (`Flusher`, `ReaderFrom`) | Define a tiny interface; check with `_, ok := v.(Iface)`. | | You want a stable plugin/SPI boundary | Yes, but keep it minimal and version it explicitly. |

> Read [references/consumer-owned-interfaces.md](references/consumer-owned-interfaces.md) when migrating a producer-defined interface back to the consumer, or when designing a new package boundary.

Accept Interfaces, Return Concrete Types

// Good — consumer defines what it needs
package notify
type Sender interface { Send(to, body string) error }
type Service struct{ s Sender }
func NewService(s Sender) *Service { return &Service{s: s} }

// Good — producer returns a concrete type
package email
type Client struct{ /* ... */ }
func New(cfg Config) *Client { /* ... */ }
func (c *Client) Send(to, body string) error { /* ... */ }
// Bad — producer defines and returns its own interface,
// forcing every consumer to depend on email.Sender.
func New(cfg Config) Sender { return &client{...} }

The exception is "expose an interface, hide the implementation": when a type has no exported methods beyond what the interface promises, returning the interface (`func NewHash() hash.Hash32`) is fine.

Keep Interfaces Small

Standard library interfaces are the model: `io.Reader`, `io.Writer`, `io.Closer`, `fmt.Stringer`, `error` — one or two methods each. Compose larger contracts:

type ReadWriteCloser interface { io.Reader; io.Writer; io.Closer }

If you find yourself writing a five-method interface, split it until each piece has a single reason to exist.

Compile-Time Satisfaction Check

var _ io.ReadWriter = (*MyBuffer)(nil)

Use when the type must satisfy an interface for correctness (custom JSON marshalling, `http.Handler`) and no other static use already enforces it. Don't add one for every interface.

Type Assertions and Type Switches

Always use the comma-ok form. Type switches re-declare the variable; cases with multiple types fall back to the interface type. Use optional-behaviour assertions to *enhance* a path without requiring the capability:

s, ok := v.(string)                              // comma-ok
switch x := v.(type) { case string: /* ... */ }  // type switch
if f, ok := w.(http.Flusher); ok { f.Flush() }   // optional behaviour

Embedding: Composition, Not Inheritance

Struct embedding promotes the inner type's methods and fields to the outer type. Use it deliberately — every promoted method becomes part of your public API.

type Server struct {
    *slog.Logger          // exposes Info/Warn/Error on Server
    addr string
}

| Use embedding when | Use a named field when | |---|---| | You want the outer type to *be* an enhanced version of the inner | You only need the inner type internally | | The full inner API should be promoted | You want to delegate explicitly to a subset |

Avoid embedding in exported types unless the promotion is the whole point. The inner type's method set is locked in once published.

> Read [references/embedding-and-receivers.md](references/embedding-and-receivers.md) when designing struct embedding, overriding promoted methods, resolving name conflicts, or choosing between pointer and value receivers.

Dependency Injection via Interfaces

Constructors take interfaces; tests pass fakes. No DI container required.

type UserStore interface {
    FindByID(ctx context.Context, id string) (*User, error)
}

type UserService struct{ store UserStore }
func NewUserService(s UserStore) *UserService { return &UserService{store: s} }

The `UserStore` interface lives in the package that defines `UserService`. The concrete `*pgUserStore` lives in a database package and doesn't know `UserService` exists.

Preventing Accidental Copies

Structs that must not be copied (those holding a mutex, internal pointers, or a `sync.WaitGroup`) should e

Read more
@muratmirgun
Ships withgophers

26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.

926 skills4 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
9
Star history of muratmirgun/gophers on GitHub, a chart by star-history.com

Other skills on gophers.

go-clean-architecture

go-clean-architecture

Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…

@muratmirgun@muratmirgunInstall Skill
go-code-review

go-code-review

Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by…

@muratmirgun@muratmirgunInstall Skill
go-code-style

go-code-style

Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function…

@muratmirgun@muratmirgunInstall Skill
go-concurrency

go-concurrency

Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker…

@muratmirgun@muratmirgunInstall Skill
go-context

go-context

Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation,…

@muratmirgun@muratmirgunInstall Skill
go-control-flow

go-control-flow

Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization,…

@muratmirgun@muratmirgunInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about go-interfaces.

Which coding agents can use go-interfaces?
go-interfaces installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.