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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
