SyntaxStudy
Sign Up
Go Interfaces and Duck Typing
Go Beginner 1 min read

Interfaces and Duck Typing

An interface in Go is a set of method signatures. Any type that implements all the methods of an interface automatically satisfies that interface — there is no explicit `implements` declaration. This is called structural typing or "duck typing": if a type walks like a duck and quacks like a duck, it is a duck. This implicit satisfaction means that interfaces can be defined after the types that satisfy them. A package can define an interface that existing types in other packages satisfy without modifying those packages. This is a powerful decoupling mechanism and is used extensively in the standard library — for example, `io.Reader` and `io.Writer` are satisfied by file handles, network connections, and in-memory buffers alike. The empty interface `interface{}` (or `any` in Go 1.18+) is satisfied by all types and can hold a value of any type. It is used when the type is not known at compile time, such as in generic container functions or when working with arbitrary JSON data. To use the underlying value, you need a type assertion or a type switch.
Example
package main

import (
    "fmt"
    "math"
)

// Shape interface — any type with these methods satisfies it
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct{ Radius float64 }
type Rect   struct{ Width, Height float64 }

func (c Circle) Area()      float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

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

// printShape accepts any Shape — polymorphism without inheritance
func printShape(s Shape) {
    fmt.Printf("%T  area=%.2f  perimeter=%.2f\n", s, s.Area(), s.Perimeter())
}

// Type switch on empty interface
func describe(v any) {
    switch t := v.(type) {
    case int:
        fmt.Printf("int: %d\n", t)
    case string:
        fmt.Printf("string: %q\n", t)
    case Shape:
        fmt.Printf("Shape with area %.2f\n", t.Area())
    default:
        fmt.Printf("unknown type: %T\n", t)
    }
}

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rect{Width: 4, Height: 6},
    }
    for _, s := range shapes {
        printShape(s)
    }

    describe(42)
    describe("hello")
    describe(Circle{Radius: 3})
    describe(true)
}