Why Go Favors Composition Over Inheritance
· 3 min read · Originally on Medium
Object-Oriented Programming (OOP) has shaped how we build software for decades. Languages like Java and C++ popularized inheritance to promote code reuse and abstraction. But when developers switch to Go (Golang), they quickly notice something missing — classical inheritance. Instead, Go takes a different route: it embraces composition.
So, how does Go approach OOP? And why does it favor composition? Let's explore.
OOP in Go: A Different Angle
Go skips traditional OOP constructs like classes but still embraces its core ideas:
- Encapsulation through exported and un-exported fields/methods
- Polymorphism using interfaces
- Code reuse via composition, not inheritance
Go encourages building behavior using structs and interfaces, focusing on modular, decoupled design.
Why Go Has No Inheritance
Go's creators deliberately avoided inheritance for several key reasons:
- Simplicity — No deep class trees to manage or untangle
- Loose Coupling — Encourages independent, testable components
- Resilience — Avoids the "fragile base class" problem
- Readability — Promotes flat, maintainable architecture
Rather than forcing rigid hierarchies, Go gives you flexible building blocks.
Composition in Practice
Instead of inheritance, Go composes structs by embedding. This lets you reuse behavior cleanly.
type Engine struct{}
func (e Engine) Start() { fmt.Println("Engine started") }
type Radio struct{}
func (r Radio) PlayMusic() { fmt.Println("Playing music") }
type Car struct {
Engine
Radio
}
func main() {
car := Car{}
car.Start() // Via Engine
car.PlayMusic() // Via Radio
}Thought: Car embeds Engine and Radio, and gains their methods—like inheritance, but simpler and safer.
Composition + Interfaces = Go's Polymorphism
Go's interfaces are powerful and implicitly satisfied — no need for an implements keyword.
type Starter interface {
Start()
}
func StartVehicle(s Starter) {
s.Start()
}
func main() {
car := Car{}
StartVehicle(car) // Car satisfies Starter via embedded Engine
}This is Go's take on duck typing: if it has a Start() method, it's a Starter.
A Real-World Analogy
Imagine building a robot.
Inheritance style: Adding new abilities like "Cleaning" requires new subclasses — hard to scale.
Go's composition style:
type Singing struct{}
type Dancing struct{}
type Robot struct {
Singing
Dancing
}Need new behavior? Just embed it. No tangled inheritance chain.
Final Thoughts
Go's approach — composition over inheritance — isn't just a syntax difference. It reflects a deeper philosophy:
- Favor simplicity
- Build small, testable units
- Design for flexibility, not hierarchy
When writing Go, think in terms of capabilities, not class trees. More often than not, composition is all you need.