Composition vs. Inheritance: What “Don’t Use Inheritance” Really Means
If you study object-oriented programming, you will inevitably encounter this sentence: “Favor composition over inheritance.”
To get straight to the point, this does not mean “never use inheritance.” It means “don’t overuse inheritance for code reuse.” Inheritance is still a valid tool, but composition is the safer choice in most situations where reuse is needed.
In this article, I’ll explain how the difference between the two appears in real code and how to choose between them, based on my experience.
Inheritance and Composition: What’s the Difference?
Let’s start with a very simple distinction.
Inheritance represents an “is-a” relationship: a dog is an animal, for example. The child inherits the parent class’s functionality as-is.
Composition represents a “has-a” relationship: a car has an engine. You keep an object with the required functionality inside another object and delegate work to it.
The difference becomes clearer in code. The following shows how functionality is inherited.
// Inheritance: Stackinherits NSMutableArrayall of its methods
class Stack: NSMutableArray {
func push(_ o: Any) { add(o) }
func pop() -> Any {
let o = lastObject!
removeLastObject()
return o
}
}
The problem is that this exposes unwanted methods such as insert(_:at:) and removeObject(at:) to the outside as well.
Here is the same thing rewritten using composition.
// Composition: delegate only the functionality you need
class Stack {
private var list: [Any] = []
func push(_ o: Any) { list.append(o) }
func pop() -> Any { list.removeLast() }
}
The array is kept inside, and only the required operations are exposed. Stack retains only what it actually needs to do.
What “Don’t Use Inheritance” Really Means
This advice comes from two major weaknesses of inheritance.
First, encapsulation breaks down. The child becomes dependent on the parent’s internal implementation. When the parent code changes, a previously working child can suddenly break.
Second, coupling becomes too tight. The parent and child are bound together at compile time, making it difficult to change the relationship later.
Inheritance is a tool for defining types, not for reusing code.
That is the key point. The moment you use inheritance merely because you want to reuse code, the relationship starts to become tangled.
A famous example is the square and rectangle. Mathematically, a square is a rectangle, so inheritance seems appropriate. But once it inherits the ability to change a rectangle’s width and height independently, the square is no longer a square.
Even if something looks like an is-a relationship, inheritance becomes a trap if the behavior cannot be fully substituted.
So When Is It Safe to Use Inheritance?
Inheritance is not inherently bad. When all of the following conditions are met, it can actually be the cleanest option.
- Is it a genuine is-a relationship: Is the child always a kind of the parent?
- Does it follow the Liskov Substitution Principle: Can the child replace the parent without causing problems?
- Was the parent designed for inheritance: Is it documented and open for extension?
If all three answers are yes, inheritance is fine. Extending framework-based classes such as UIViewController is a typical example.
If even one point is unclear, consider composition first.
Here is a simple comparison in table form.
| Situation | Recommendation |
|---|---|
| Pure is-a relationship with complete substitutability | Inheritance |
| When you simply want to reuse code | Composition |
| When you want to change behavior at runtime | Composition |
| When you need to combine multiple capabilities | Composition |
As you can see, most situations encountered in practice lean toward composition. The advice to “favor composition” exists for a reason.
How I Make This Decision in Practice
When creating a new class, I habitually ask first: “Is this a kind of the parent, or do I simply want to borrow the parent’s functionality?”
If I only want to borrow functionality, I almost always choose composition.
Design patterns point in the same direction. Patterns such as Strategy and Decorator are all based on composition.
The Strategy pattern, in particular, separates behavior into objects and swaps them at runtime. It offers flexibility that is difficult to reproduce with inheritance.
Of course, you have to accept that the code becomes slightly longer. You need to write the delegating methods yourself. Even so, changing the structure later becomes much easier.
“Don’t use inheritance” does not ban inheritance; it warns against overusing it for reuse. If the is-a relationship is clear, use inheritance. If you merely want to borrow functionality, use composition. Keeping this one rule in mind can make your code much more robust. I hope you continue to design great software!
Recommended Reading
- OOP Abstraction and Encapsulation: If You’ve Always Found Them Confusing, End the Confusion with This Article
- The Real Reason to Add the Swift final Keyword to a Class: A Complete Guide to Performance and Design
- The Essence of Object-Oriented Programming Is Messaging: The Real OOP According to Alan Kay

