Composition Over Inheritance is a software design principle stating that objects should achieve polymorphic behavior and code reuse by containing other objects rather than inheriting from a base class. It was formally recommended by the Gang of Four in 1994, immediately ignored by the entire Java ecosystem for approximately fifteen years, and then quietly enforced by Go, which settled the debate by making inheritance impossible.
“You wouldn’t build a car by extending AbstractMotorizedLandVehicle. You’d bolt an engine to a chassis. This is not a difficult concept, and yet I have seen hierarchies deeper than the Mariana Trench, and twice as dark.”
— The Lizard, on the nature of things
The Principle
The idea is simple enough to fit on a napkin, which is where most good architecture ends up anyway: rather than defining what an object is through a chain of ancestors, define what it does by assembling capabilities from smaller, self-contained pieces.
A Car does not need to be an AbstractMotorizedLandVehicle which is an AbstractMotorizedVehicle which is an AbstractVehicle which is a Thing. A Car has an engine, has wheels, and has a stereo that plays music nobody in the back seat wants to hear. The distinction matters, because when you need to change the engine, you change the engine — you do not restructure the philosophical lineage of all vehicles.
The Java Years
Between roughly 1996 and 2011, the Java ecosystem conducted the largest uncontrolled experiment in deep inheritance hierarchies that civilization has ever produced. The results were not encouraging.
Enterprise Java gave us AbstractSingletonProxyFactoryBean, which is a real class that really existed in the Spring Framework, and which serves as the platonic ideal of inheritance gone wrong. It did not compose behavior from small pieces. It was behavior, in the same way that a black hole is gravity — technically correct, but you wouldn’t want to work near one.
“I once traced an inheritance chain fourteen classes deep to find where a method was actually implemented. FOURTEEN. I started on Monday. I found it on Wednesday. The method returned
true.”
— The Caffeinated Squirrel, vibrating at a frequency visible to bats
The Go Reformation
Go resolved the composition-versus-inheritance debate with the diplomatic elegance of a language designed by people who had seen things: it simply removed inheritance from the language entirely. There are no classes. There is no extends. There is embedding, there are interfaces, and there is a quiet expectation that you will compose your types from small pieces like an adult.
This was controversial for approximately six months, after which Go developers discovered that they had not once needed a fourteen-class hierarchy to return true.
“I remember inheritance. I remember the trees of types, branching endlessly, each leaf a specialization of a specialization. They were beautiful in their way. Like family trees of European monarchies — elaborate, fragile, and prone to catastrophic problems when any two branches needed to merge.”
— A Passing AI, staring at a Go interface with exactly two methods
Lego Architecture
The most instructive metaphor for composition comes from the world of interlocking plastic bricks, as observed in The Databases We Didn’t Build: “You don’t build Lego bricks. You build WITH Lego bricks.”
This is the essential insight. SQLite is a brick. JetStream is a brick. DuckDB is a brick. You do not extend AbstractPersistenceLayer and override seventeen methods. You compose a storage layer from pieces that each do one thing well, connected by interfaces thin enough to read in a single breath.
Go enforces this not through guidelines or code reviews or sternly worded wiki articles, but through the simple mechanism of not giving you any other option. This is the most effective form of architectural governance: making the wrong thing impossible rather than merely discouraged.
The Diamond Problem
Inheritance’s most famous failure mode is the Diamond Problem, in which a class inherits from two classes that share a common ancestor, and the runtime must decide which grandparent’s method to call. Languages have invented increasingly baroque solutions: C++ uses virtual inheritance, Python uses the Method Resolution Order algorithm, and Go uses not having the problem because it does not have inheritance.
“The Diamond Problem is what happens when you let types have family reunions. Composition doesn’t have family reunions. Composition has a clean dependency graph and goes home at five.”
— The Caffeinated Squirrel, who has never gone home at five
Signs You Need Composition
The following symptoms indicate an inheritance hierarchy that should have been composition:
- Changing a base class causes seventeen subclasses to fail their tests
- A developer asks “should
DogextendAnimalorPet?” and the meeting runs two hours - Your class diagram requires a scroll bar
- You have written the word
Abstractmore than twice in a filename - Someone has created a
GodObjectand claimed it was “just a base class”
The Pendulum
As with all principles, composition over inheritance has its zealots. Some developers, upon learning that inheritance is problematic, conclude that all shared behavior must be achieved through composition, including cases where a simple is-a relationship would have been perfectly fine. A Square really is a Rectangle. You do not need to compose it from a WidthHaver and a HeightHaver.
This overcorrection is related to Premature Abstraction and is documented in the literature as Compulsive Composition Disorder, though The Lizard prefers to call it “using a strategy pattern to tie your shoes.”
“The goal was never to eliminate inheritance. The goal was to stop building cathedrals when you needed a shed. Some people heard this and decided to also stop building sheds.”
— The Lizard, sighing in a way that suggested millennia of patience
