home / skills / thebushidocollective / han / go-interfaces

This skill helps you design flexible Go APIs and abstractions by applying clean interface patterns, composition, and dependency injection.

npx playbooks add skill thebushidocollective/han --skill go-interfaces

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
12.1 KB
---
name: go-interfaces
user-invocable: false
description: Use when Go interfaces including interface design, duck typing, and composition patterns. Use when designing Go APIs and abstractions.
allowed-tools:
  - Bash
  - Read
---

# Go Interfaces

Master Go's interface system for creating flexible, decoupled code through
implicit implementation and composition patterns.

## Basic Interfaces

**Defining and implementing interfaces:**

```go
package main

import "fmt"

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

// Implement interface (implicit)
type ConsoleWriter struct{}

func (cw ConsoleWriter) Write(p []byte) (n int, err error) {
    fmt.Print(string(p))
    return len(p), nil
}

func main() {
    var w Writer = ConsoleWriter{}
    w.Write([]byte("Hello, World!\n"))
}
```

**Multiple methods in interface:**

```go
type Reader interface {
    Read(p []byte) (n int, err error)
}

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

// Implement ReadWriter
type File struct {
    name string
}

func (f *File) Read(p []byte) (n int, err error) {
    // Implementation
    return 0, nil
}

func (f *File) Write(p []byte) (n int, err error) {
    // Implementation
    return len(p), nil
}
```

## Empty Interface

**Using interface{} (any in Go 1.18+):**

```go
// Accepts any type
func printValue(v interface{}) {
    fmt.Println(v)
}

// Modern syntax (Go 1.18+)
func printAny(v any) {
    fmt.Println(v)
}

func main() {
    printValue(42)
    printValue("hello")
    printValue(true)

    printAny(3.14)
}
```

**Type assertions:**

```go
func processValue(v interface{}) {
    // Type assertion
    if str, ok := v.(string); ok {
        fmt.Println("String:", str)
    }

    // Type switch
    switch val := v.(type) {
    case int:
        fmt.Println("Integer:", val)
    case string:
        fmt.Println("String:", val)
    case bool:
        fmt.Println("Boolean:", val)
    default:
        fmt.Println("Unknown type")
    }
}
```

## Interface Composition

**Embedding interfaces:**

```go
type Reader interface {
    Read(p []byte) (n int, err error)
}

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

type Closer interface {
    Close() error
}

// Compose interfaces
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// Standard library example
import "io"

func useReadWriteCloser(rwc io.ReadWriteCloser) {
    // Can call Read, Write, and Close
    rwc.Write([]byte("data"))
    rwc.Close()
}
```

## Common Interfaces

**Standard library interfaces:**

```go
// Stringer interface
type Stringer interface {
    String() string
}

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}

// error interface
type error interface {
    Error() string
}

type MyError struct {
    Message string
}

func (e MyError) Error() string {
    return e.Message
}

// sort.Interface
type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
```

## Interface Design Patterns

**Small interfaces:**

```go
// Good: small, focused interfaces
type Getter interface {
    Get(key string) (value string, exists bool)
}

type Setter interface {
    Set(key, value string)
}

type Deleter interface {
    Delete(key string)
}

// Compose as needed
type Cache interface {
    Getter
    Setter
    Deleter
}
```

**Accept interfaces, return structs:**

```go
// Accept interface parameter
func processReader(r io.Reader) error {
    data, err := io.ReadAll(r)
    if err != nil {
        return err
    }
    fmt.Println(string(data))
    return nil
}

// Return concrete type
func newConfig() *Config {
    return &Config{
        Host: "localhost",
        Port: 8080,
    }
}

type Config struct {
    Host string
    Port int
}
```

## Nil Interfaces

**Understanding nil interfaces:**

```go
func checkNil() {
    var i interface{}
    fmt.Println(i == nil) // true

    var p *Person
    i = p
    fmt.Println(i == nil) // false! (type is set, value is nil)

    // Proper nil check
    v, ok := i.(*Person)
    fmt.Println(v == nil, ok) // true, true
}
```

## Interface Satisfaction

**Checking interface implementation:**

```go
// Compile-time check
var _ io.Writer = (*MyWriter)(nil)
var _ io.Reader = (*MyReader)(nil)

type MyWriter struct{}

func (w *MyWriter) Write(p []byte) (n int, err error) {
    return len(p), nil
}

// If MyWriter doesn't implement Writer, compilation fails
```

## Duck Typing

**Implicit interface satisfaction:**

```go
// No explicit "implements" keyword needed
type Duck interface {
    Quack()
    Walk()
}

type RealDuck struct{}

func (d RealDuck) Quack() {
    fmt.Println("Quack!")
}

func (d RealDuck) Walk() {
    fmt.Println("Waddle waddle")
}

type Robot struct{}

func (r Robot) Quack() {
    fmt.Println("Beep boop quack")
}

func (r Robot) Walk() {
    fmt.Println("*mechanical walking sounds*")
}

func makeDuckDoThings(d Duck) {
    d.Quack()
    d.Walk()
}

func main() {
    makeDuckDoThings(RealDuck{})
    makeDuckDoThings(Robot{})
}
```

## Polymorphism

**Using interfaces for polymorphism:**

```go
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return 3.14159 * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * 3.14159 * c.Radius
}

func printShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n",
        s.Area(), s.Perimeter())
}

func main() {
    shapes := []Shape{
        Rectangle{Width: 10, Height: 5},
        Circle{Radius: 7},
    }

    for _, shape := range shapes {
        printShapeInfo(shape)
    }
}
```

## Dependency Injection

**Using interfaces for testability:**

```go
// Define interface for dependency
type UserRepository interface {
    GetUser(id int) (*User, error)
    SaveUser(user *User) error
}

// Production implementation
type PostgresUserRepo struct {
    db *sql.DB
}

func (r *PostgresUserRepo) GetUser(id int) (*User, error) {
    // Database query
    return &User{}, nil
}

func (r *PostgresUserRepo) SaveUser(user *User) error {
    // Database insert/update
    return nil
}

// Test implementation
type MockUserRepo struct {
    users map[int]*User
}

func (m *MockUserRepo) GetUser(id int) (*User, error) {
    user, exists := m.users[id]
    if !exists {
        return nil, errors.New("user not found")
    }
    return user, nil
}

func (m *MockUserRepo) SaveUser(user *User) error {
    m.users[user.ID] = user
    return nil
}

// Service depends on interface, not concrete type
type UserService struct {
    repo UserRepository
}

func (s *UserService) GetUserName(id int) (string, error) {
    user, err := s.repo.GetUser(id)
    if err != nil {
        return "", err
    }
    return user.Name, nil
}

type User struct {
    ID   int
    Name string
}
```

## Builder Pattern with Interfaces

**Fluent interface pattern:**

```go
type QueryBuilder interface {
    Select(fields ...string) QueryBuilder
    From(table string) QueryBuilder
    Where(condition string) QueryBuilder
    Build() string
}

type sqlQueryBuilder struct {
    selectFields []string
    fromTable    string
    whereClause  string
}

func NewQueryBuilder() QueryBuilder {
    return &sqlQueryBuilder{}
}

func (b *sqlQueryBuilder) Select(fields ...string) QueryBuilder {
    b.selectFields = fields
    return b
}

func (b *sqlQueryBuilder) From(table string) QueryBuilder {
    b.fromTable = table
    return b
}

func (b *sqlQueryBuilder) Where(condition string) QueryBuilder {
    b.whereClause = condition
    return b
}

func (b *sqlQueryBuilder) Build() string {
    query := "SELECT " + strings.Join(b.selectFields, ", ")
    query += " FROM " + b.fromTable
    if b.whereClause != "" {
        query += " WHERE " + b.whereClause
    }
    return query
}

func main() {
    query := NewQueryBuilder().
        Select("id", "name", "email").
        From("users").
        Where("age > 18").
        Build()

    fmt.Println(query)
}
```

## Strategy Pattern

**Implementing strategy pattern:**

```go
type PaymentStrategy interface {
    Pay(amount float64) error
}

type CreditCardPayment struct {
    CardNumber string
}

func (c *CreditCardPayment) Pay(amount float64) error {
    fmt.Printf("Paying %.2f with credit card %s\n",
        amount, c.CardNumber)
    return nil
}

type PayPalPayment struct {
    Email string
}

func (p *PayPalPayment) Pay(amount float64) error {
    fmt.Printf("Paying %.2f via PayPal to %s\n",
        amount, p.Email)
    return nil
}

type ShoppingCart struct {
    paymentMethod PaymentStrategy
}

func (cart *ShoppingCart) SetPaymentMethod(pm PaymentStrategy) {
    cart.paymentMethod = pm
}

func (cart *ShoppingCart) Checkout(amount float64) error {
    return cart.paymentMethod.Pay(amount)
}

func main() {
    cart := &ShoppingCart{}

    cart.SetPaymentMethod(&CreditCardPayment{CardNumber: "1234-5678"})
    cart.Checkout(100.00)

    cart.SetPaymentMethod(&PayPalPayment{Email: "[email protected]"})
    cart.Checkout(50.00)
}
```

## Adapter Pattern

**Adapting interfaces:**

```go
// Third-party logger
type ThirdPartyLogger struct{}

func (t *ThirdPartyLogger) LogMessage(msg string, level int) {
    fmt.Printf("[Level %d] %s\n", level, msg)
}

// Our application interface
type Logger interface {
    Info(msg string)
    Error(msg string)
}

// Adapter
type LoggerAdapter struct {
    thirdParty *ThirdPartyLogger
}

func (a *LoggerAdapter) Info(msg string) {
    a.thirdParty.LogMessage(msg, 0)
}

func (a *LoggerAdapter) Error(msg string) {
    a.thirdParty.LogMessage(msg, 2)
}

func useLogger(logger Logger) {
    logger.Info("Application started")
    logger.Error("An error occurred")
}

func main() {
    adapter := &LoggerAdapter{
        thirdParty: &ThirdPartyLogger{},
    }
    useLogger(adapter)
}
```

## When to Use This Skill

Use go-interfaces when you need to:

- Define contracts for behavior without implementation
- Enable polymorphism and code reuse
- Create testable code with dependency injection
- Implement design patterns (strategy, adapter, etc.)
- Build plugin systems or extensible architectures
- Decouple components in large applications
- Mock dependencies in tests
- Follow SOLID principles in Go
- Create flexible, maintainable APIs
- Support multiple implementations of same behavior

## Best Practices

- Keep interfaces small and focused (1-3 methods)
- Accept interfaces, return concrete types
- Define interfaces where they're used, not implemented
- Use interface composition for complex interfaces
- Don't use empty interface unless absolutely necessary
- Verify interface implementation at compile time
- Document expected behavior in interface comments
- Prefer many small interfaces over large ones
- Use standard library interfaces when applicable
- Name interfaces with -er suffix (Reader, Writer, etc.)

## Common Pitfalls

- Making interfaces too large or generic
- Defining unused interfaces "just in case"
- Returning interfaces instead of concrete types
- Not checking for nil interface values properly
- Over-abstracting simple code
- Forgetting that interfaces are satisfied implicitly
- Using empty interface excessively
- Not documenting interface contracts
- Creating interfaces for single implementation
- Confusing nil value vs nil interface

## Resources

- [Effective Go - Interfaces](https://go.dev/doc/effective_go#interfaces)
- [Go Blog - Laws of Reflection](https://go.dev/blog/laws-of-reflection)
- [Interface Documentation](https://go.dev/ref/spec#Interface_types)
- [Go by Example - Interfaces](https://gobyexample.com/interfaces)
- [Accept Interfaces, Return Structs](https://bryanftan.medium.com/accept-interfaces-return-structs-in-go-d4cab29a301b)

Overview

This skill explains practical use of Go interfaces to design flexible, decoupled APIs using implicit implementation, composition, and common design patterns. It highlights idiomatic patterns—small interfaces, accept-interfaces-return-concrete, dependency injection—and shows how interfaces enable testability and polymorphism. The material focuses on concrete outcomes: cleaner abstractions, easier testing, and extensible architectures.

How this skill works

The skill inspects how types implicitly satisfy interface contracts, how to compose interfaces by embedding, and how to use type assertions and switches for runtime type handling. It demonstrates common standard-library interfaces, compile-time implementation checks, and patterns such as strategy, adapter, builder, and dependency injection. Examples show when to accept interfaces versus when to return concrete types and how to avoid nil-interface pitfalls.

When to use it

  • Define contracts for behavior without exposing implementation details
  • Enable polymorphism and code reuse across multiple implementations
  • Make components testable via dependency injection and mocks
  • Design plugin systems, adapters, or extensible APIs
  • Compose behaviors from small interfaces for clearer abstractions
  • Avoid over-abstracting simple code and reduce coupling

Best practices

  • Keep interfaces small and focused (ideally 1–3 methods)
  • Accept interfaces in function parameters; return concrete types for clarity
  • Define interfaces where they are consumed, not where they are implemented
  • Use interface composition to build richer contracts from small pieces
  • Prefer standard library interfaces when applicable (io.Reader, fmt.Stringer, error)
  • Verify implementations at compile time with var _ Interface = (*Type)(nil)

Example use cases

  • File IO components accepting io.Reader/io.Writer to process data streams
  • Service layer depending on a repository interface for easy unit testing with mocks
  • Payment processing that switches strategy implementations (credit card, PayPal) at runtime
  • Adapter wrapping a third-party library to match your Logger interface
  • Builder/Fluent APIs implemented via an interface-backed concrete builder

FAQ

When should I use the empty interface (interface{})?

Use interface{} sparingly for truly generic data (e.g., untyped JSON). Prefer concrete or small focused interfaces to keep code safe and self-documenting.

Should I return interfaces from constructors?

Generally no. Return concrete types and accept interfaces. Returning concrete types preserves implementation details and keeps callers flexible while preventing accidental API constraints.