Fundamentals 13 min read

Understanding How Interfaces Work in Go

This article explains Go's interface type, covering its basic concept, syntax, simple and advanced examples—including empty interfaces, interface composition, standard library usage, sorting, error handling, and dependency injection—while also discussing implementation details like value vs. pointer receivers and summarizing key characteristics.

Golang Shines
Golang Shines
Golang Shines
Understanding How Interfaces Work in Go

1. Basic Concept

In Go, an interface is a type that defines a set of method signatures. Any type that implements all those methods is considered to satisfy the interface.

Syntax

type InterfaceName interface {
    Method1(parameters) returnType
    Method2(parameters) returnType
}

2. Simple Example

Define an interface Speaker with a Speak() method, then implement it for Dog and Cat structs.

package main

import "fmt"

type Speaker interface {
    Speak() string
}

type Dog struct { Name string }
func (d Dog) Speak() string { return fmt.Sprintf("%s says: woof!", d.Name) }

type Cat struct { Name string }
func (c Cat) Speak() string { return fmt.Sprintf("%s says: meow!", c.Name) }

func main() {
    var speaker Speaker
    dog := Dog{"Wangcai"}
    speaker = dog
    fmt.Println(speaker.Speak())
    cat := Cat{"Mimi"}
    speaker = cat
    fmt.Println(speaker.Speak())
}

3. Empty Interface

The empty interface interface{} contains no methods, so every type implements it.

package main

import "fmt"

func printAnything(value interface{}) {
    fmt.Printf("value: %v, type: %T
", value, value)
}

func main() {
    printAnything(42)          // int
    printAnything(3.14)        // float64
    printAnything("Hello")    // string
    printAnything([]int{1,2,3}) // []int
    var val interface{} = "Go language"
    if str, ok := val.(string); ok {
        fmt.Printf("It's a string: %s
", str)
    }
    switch v := val.(type) {
    case int:
        fmt.Printf("int: %d
", v)
    case string:
        fmt.Printf("string: %s
", v)
    default:
        fmt.Printf("unknown type: %T
", v)
    }
}

4. Interface Composition

Go allows combining multiple interfaces into a new one.

type Mover interface { Move() }
type Eater interface { Eat() }

type Animal interface {
    Mover
    Eater
}

type Bird struct { Name string }
func (b Bird) Move() { fmt.Printf("%s is flying
", b.Name) }
func (b Bird) Eat()  { fmt.Printf("%s is eating insects
", b.Name) }

func main() {
    var animal Animal = Bird{"Sparrow"}
    animal.Move()
    animal.Eat()
}

5. Practical Examples

5.1 Standard Library io.Reader and io.Writer

type CustomBuffer struct { buf bytes.Buffer }
func (cb *CustomBuffer) Write(p []byte) (int, error) { return cb.buf.Write(p) }
func (cb *CustomBuffer) Read(p []byte) (int, error)  { return cb.buf.Read(p) }

func processData(r io.Reader, w io.Writer) error {
    data := make([]byte, 1024)
    n, err := r.Read(data)
    if err != nil && err != io.EOF { return err }
    processed := strings.ToUpper(string(data[:n]))
    _, err = w.Write([]byte(processed))
    return err
}

5.2 Sorting Interface

type Person struct { Name string; Age int }
type ByAge []Person
func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

// Similar ByName implements Len, Swap, Less based on Name.
// Demonstrates sort.Sort, sort.Slice and prints results.

5.3 Error‑Handling Interface

type NetworkError struct { Op string; Code int; Message string; Time time.Time }
func (e *NetworkError) Error() string { return fmt.Sprintf("[%s] %s: %s (code: %d)", e.Time.Format("2006-01-02 15:04:05"), e.Op, e.Message, e.Code) }
func (e *NetworkError) IsTimeout() bool { return e.Code == 408 }

func fetchData() error { return &NetworkError{Op:"GET /api/data", Code:408, Message:"request timeout", Time:time.Now()} }

func main() {
    err := fetchData()
    if err != nil {
        fmt.Println("error:", err)
        var netErr *NetworkError
        if errors.As(err, &netErr) {
            fmt.Printf("network error code: %d
", netErr.Code)
            fmt.Printf("is timeout: %v
", netErr.IsTimeout())
        }
        if errors.Is(err, &NetworkError{Code:408}) {
            fmt.Println("this is a timeout error")
        }
    }
}

5.4 Dependency Injection

type Storage interface {
    Save(data string) error
    Get(id int) (string, error)
}

type MemoryStorage struct { data map[int]string }
func (m *MemoryStorage) Save(data string) error { id := len(m.data); m.data[id] = data; fmt.Printf("saved to memory: ID=%d, Data=%s
", id, data); return nil }
func (m *MemoryStorage) Get(id int) (string, error) { if v, ok := m.data[id]; ok { return v, nil } return "", fmt.Errorf("data not found") }

type DatabaseStorage struct { connection string }
func (d *DatabaseStorage) Save(data string) error { fmt.Printf("saved to database: %s
", data); return nil }
func (d *DatabaseStorage) Get(id int) (string, error) { return fmt.Sprintf("db data #%d", id), nil }

type Service struct { storage Storage }
func NewService(s Storage) *Service { return &Service{storage:s} }
func (s *Service) Process(data string) error { return s.storage.Save(data) }

func main() {
    mem := &MemoryStorage{data: make(map[int]string)}
    svc1 := NewService(mem)
    svc1.Process("first data")
    svc1.Process("second data")
    db := &DatabaseStorage{connection:"localhost:5432"}
    svc2 := NewService(db)
    svc2.Process("important data")
}

6. Advanced Usage

Shows how interfaces enable dependency injection, middleware, and plugin architectures.

7. Important Considerations

Value Receiver vs. Pointer Receiver

When an interface method has a value receiver, both value and pointer types satisfy the interface; with a pointer receiver, only the pointer type satisfies it.

type Shape interface { Area() float64 }

type Circle struct { Radius float64 }
func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius }

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

func main() {
    var s Shape
    c1 := Circle{Radius:5}
    s = c1 // OK
    fmt.Println("Circle area:", s.Area())
    c2 := &Circle{Radius:3}
    s = c2 // OK
    fmt.Println("Circle area:", s.Area())
    r1 := &Rectangle{Width:4, Height:5}
    s = r1 // OK
    fmt.Println("Rectangle area:", s.Area())
    // r2 := Rectangle{Width:2, Height:3}
    // s = r2 // compile error: Rectangle does not implement Shape
}

8. Summary

Implicit implementation : Types need not declare that they implement an interface.

Duck typing : "If it walks like a duck and quacks like a duck, it’s a duck."

Composition over inheritance : Build complex behavior by composing interfaces.

Zero value usable : The zero value of an interface is nil.

Runtime polymorphism : The concrete method called is determined at runtime.

Interfaces are a core feature of Go, providing flexible polymorphism that underpins patterns such as dependency injection, middleware, and plugin architectures.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

godependency injectionerror handlinginterfacepolymorphismcomposition
Golang Shines
Written by

Golang Shines

We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.