Interfaces
Go structural typing vs Python duck typing — explicit vs implicit
Introduction
In this lesson, you'll learn about interfaces in Go. Coming from Python, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Python, you're familiar with go structural typing vs python duck typing — explicit vs implicit.
Go has its own approach to go structural typing vs python duck typing — explicit vs implicit, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import "fmt"
// Interface — structural like Python Protocol
type Animal interface {
Speak() string
Move() string
}
// No "implements" keyword — satisfy implicitly
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof" }
func (d Dog) Move() string { return "runs" }
type Robot struct{}
func (r Robot) Speak() string { return "Beep" }
func (r Robot) Move() string { return "rolls" }
func MakeNoise(a Animal) {
fmt.Println(a.Speak())
}
// Interface composition
type Stringer interface { String() string }
type Named interface { Name() string }
type NamedStringer interface { Stringer; Named }
// Empty interface — any type
func PrintAny(v any) { fmt.Println(v) }
// Type assertion
func TypeCheck(v any) {
if s, ok := v.(string); ok {
fmt.Println("string:", s)
}
}
func main() {
MakeNoise(Dog{"Rex"}) // Woof
MakeNoise(Robot{}) // Beep
}Comparing to Python
Here's how you might have written similar code in Python:
from typing import Protocol
# Protocol: structural typing (Python 3.8+)
class Animal(Protocol):
def speak(self) -> str: ...
def move(self) -> str: ...
# Any class with these methods satisfies Animal
class Dog:
def speak(self) -> str: return "Woof"
def move(self) -> str: return "runs"
class Robot:
def speak(self) -> str: return "Beep"
def move(self) -> str: return "rolls"
def make_noise(a: Animal) -> None:
print(a.speak())
# Works at runtime without explicit declaration
make_noise(Dog()) # Woof
make_noise(Robot()) # Beep
# Abstract base class (nominal typing)
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...You may be used to different syntax or behavior.
Both Go and Python use structural typing — no explicit 'implements' needed
You may be used to different syntax or behavior.
Go interfaces are compile-time checked; Python duck typing is checked at runtime
You may be used to different syntax or behavior.
Python Protocol requires importing and declaring; Go interface is just a type declaration
You may be used to different syntax or behavior.
Go interface composition (embedding) vs Python multiple Protocol inheritance
You may be used to different syntax or behavior.
'any' in Go = interface{} = Python's Any type hint
Step-by-Step Breakdown
1. Implicit Satisfaction
Like Python duck typing, Go satisfies interfaces automatically. Unlike Python, Go checks this at compile time.
class Dog:
def speak(self): return "Woof"
# Implicitly satisfies Animal Protocoltype Sayer interface { Say() string }
type Dog struct{}
func (d Dog) Say() string { return "Woof" }
// Dog implicitly implements Sayer — compile-time checked2. Interface Composition
Embed interfaces to compose larger ones. This is more common in Go than Python's Protocol inheritance.
class Combined(Protocol):
def read(self) -> str: ...
def write(self, s: str) -> None: ...type Reader interface { Read(p []byte) (int, error) }
type Writer interface { Write(p []byte) (int, error) }
type ReadWriter interface { Reader; Writer } // compose3. Type Assertions
Use type assertions to extract the concrete type from an interface. Always use the two-value form to avoid panics.
if isinstance(v, str):
print(v.upper())if s, ok := v.(string); ok {
fmt.Println(strings.ToUpper(s))
}
// Type switch
switch t := v.(type) {
case string: fmt.Println("str:", t)
case int: fmt.Println("int:", t)
default: fmt.Println("other")
}4. io.Reader / io.Writer Pattern
Go's stdlib defines small, composable interfaces like io.Reader and io.Writer. Code that accepts these works with files, network connections, buffers, etc.
from typing import IO
def process(f: IO[str]) -> str:
return f.read()import "io"
func Process(r io.Reader) ([]byte, error) {
return io.ReadAll(r)
}
// Works with: os.File, bytes.Buffer, net.Conn, strings.ReaderCommon Mistakes
When coming from Python, developers often make these mistakes:
- Both Go and Python use structural typing — no explicit 'implements' needed
- Go interfaces are compile-time checked; Python duck typing is checked at runtime
- Python Protocol requires importing and declaring; Go interface is just a type declaration
Key Takeaways
- Go interfaces are satisfied implicitly — like Python Protocol, but compile-time checked
- Compose interfaces by embedding: type RW interface { Reader; Writer }
- Type assertion: v.(Type) — use two-value form (v, ok) to avoid panic
- io.Reader/Writer are the fundamental Go interfaces — accept them instead of concrete file types