Go
Interfaces
Structs & Methods showed Go has no inheritance. So how does Go get the "treat different types uniformly" benefit that C++'s polymorphism provides? Interfaces — a completely different mechanism, with a genuinely surprising property: a type never declares which interfaces it implements. It just does, or it doesn't, based purely on which methods it has.
- What it is
- A set of method signatures — any type with all of those methods automatically satisfies the interface, with no explicit declaration required.
- Mental model
- C++ polymorphism is a family tree — a type explicitly says which base class it belongs to. A Go interface is a duck test: if it has the right methods, it qualifies, no family relationship needed at all.
- Key numbers
- The empty interface
interface{}(or its aliasany) is satisfied by literally every type — Go's escape hatch for "accepts anything." - Reach for it when
- A function needs to work across multiple types that share behavior but not a common base type — exactly where C++ would reach for an abstract base class and virtual functions.
Defining and implementing an interface
type Shape interface {
Area() float64 // any type with an Area() float64 method satisfies Shape
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Square struct {
Side float64
}
func (s Square) Area() float64 { return s.Side * s.Side }
func printArea(s Shape) {
fmt.Println(s.Area())
}
printArea(Circle{Radius: 5}) // 78.5397
printArea(Square{Side: 4}) // 16 — same function, different concrete type
Implicit implementation — the big difference
Notice what's missing from Circle and Square above: neither
one ever wrote implements Shape anywhere. Compare this directly to
C++, where a class has to
explicitly derive from an abstract base class:
// C++: EXPLICIT — Circle must say it derives from Shape
class Circle : public Shape {
float area() override { ... }
};
// Go: IMPLICIT — Circle satisfies Shape just by having the right method, no declaration
type Circle struct { Radius float64 }
func (c Circle) Area() float64 { ... } // that's it — Circle IS a Shape now, automatically
This means a type can satisfy an interface it was never written with in mind — even one defined in a completely different package, written by someone else, years later. It's a genuinely different way of thinking about "what type is this" than C++'s explicit class hierarchies.
The empty interface
func describe(v interface{}) { // interface{} (or "any") accepts a value of ANY type
fmt.Println(v)
}
describe(42)
describe("hello")
describe(Circle{Radius: 5})
An interface with zero required methods is satisfied by every type, since there's nothing to fail to implement — Go's version of a fully generic parameter, used before Go added true generics, and still common in APIs that genuinely need to accept anything.
Type assertions
Given an interface value, you can ask what concrete type is actually behind it — Go's version of
C++'s dynamic_cast, but returning
a value and a boolean instead of a null pointer on failure:
var s Shape = Circle{Radius: 5}
circle, ok := s.(Circle) // "is s actually a Circle?"
if ok {
fmt.Println(circle.Radius) // 5
}
square, ok := s.(Square) // s is NOT a Square
if !ok {
fmt.Println("not a square") // not a square
}
This (value, ok) shape is the same pattern from
Functions & Multiple Return Values —
Go reuses this one idea everywhere instead of introducing a separate mechanism per feature.
Real-world applications
io.Readerandio.Writer— two of Go's most important standard-library interfaces, satisfied by files, network connections, in-memory buffers, and more, letting the same code read from or write to any of them uniformly.erroritself — Go's built-inerrortype is just an interface with one method,Error() string— the subject of the upcoming Error Handling chapter.- Mocking in tests — defining an interface for a dependency (a database, an API client) makes it trivial to substitute a fake implementation in tests, without any inheritance relationship required.
Gotchas
- A failed type assertion without the
, okform (justcircle := s.(Circle)) panics at runtime if the assertion is wrong — always use the two-value form unless you're certain of the type. - Overusing
interface{}/anythrows away Go's compile-time type checking, the same tradeoff as C'svoid*— reach for a specific interface with real method requirements whenever you can, not a blanket "accepts anything." - A type can accidentally satisfy an interface it wasn't meant to, purely by happening to have a method with the right name and signature — implicit implementation is powerful but occasionally surprising in a large codebase.
- Unlike a C++ abstract class, a Go interface carries zero data of its own and cannot have a default method implementation — it is purely a set of required method signatures, nothing more.