Composition vs Inheritance
Summary
Inheritance models an "is-a" relationship by having a subclass extend a base class, inheriting its members automatically. Composition models a "has-a" relationship by building an object out of smaller, independent parts, and delegating to them instead of inheriting their behavior. Both are ways to reuse and organize behavior across types — the decision between them is one of the most consequential design choices in an object-oriented codebase, because inheritance chosen too eagerly is far harder to unwind later than composition is.
Why This Matters
Inheritance looks convenient at first: a FlyingEnemy : Enemy gets
everything Enemy has for free. But a single inheritance chain forces
every subclass to fit into one rigid hierarchy — the moment a design needs
an enemy that both flies and swims, or a FlyingEnemy that later needs
to stop flying temporarily, a strict class hierarchy has no clean way to
express that without duplicating code or forcing awkward, unrelated base
classes. Composition avoids this by attaching a FlightBehavior and a
SwimBehavior to any object that needs them, independently of what else
that object is — mixing and changing capabilities without redesigning a
class hierarchy.
Mental Model
Inheritance is a family tree; composition is a toolbox. A family tree
fixes your relationship to your ancestors permanently — you cannot
reassign your parent, and every trait you inherit comes bundled together,
whether you want all of it or not. A toolbox, by contrast, lets you pick up
exactly the tools a given job needs, put one down and pick up a different
one later, and combine tools freely regardless of which "family" they came
from. Composition-based design (famously embodied by Unity's own
Component system) treats behavior like tools in a box, attached to an
object as needed, rather than baked permanently into a rigid lineage.
Core Explanation
- Inheritance is "is-a," fixed at compile time, single-chain in
C#/C++/most OOP languages. A
FlyingEnemyis anEnemy— this relationship cannot change at runtime, and a class can typically extend only one base class, forcing every capability into one linear hierarchy. - Composition is "has-a," assembled from independent parts, often
changeable at runtime. A
GameObjecthas aHealthComponent, has aMovementComponent— these parts don't need to know about each other, can be added or removed independently, and don't require a shared ancestor. - The classic failure mode of deep inheritance is the "diamond" or rigid-hierarchy problem. Needing a type that combines two unrelated capabilities (flying + swimming) forces either duplicated code across two separate subclasses, or an awkward common ancestor that doesn't actually make sense for every type that needs it.
- Composition trades some convenience for flexibility. Composed
objects need explicit delegation (
healthComponent.TakeDamage(...)rather than inheritedTakeDamage(...)), which is a little more code up front, in exchange for capabilities that mix and change freely.
Syntax / Theory
C# — Inheritance approach
public class Enemy : MonoBehaviour { public virtual void Attack() { } }
public class FlyingEnemy : Enemy { public void Fly() { } }
public class SwimmingEnemy : Enemy { public void Swim() { } }
// A flying-AND-swimming enemy has no clean home in this hierarchy.
C# — Composition approach (Unity's own model)
public class FlightBehavior : MonoBehaviour { public void Fly() { } }
public class SwimBehavior : MonoBehaviour { public void Swim() { } }
public class HealthBehavior : MonoBehaviour { public void TakeDamage(int amount) { } }
// A single GameObject picks up exactly the components it needs.
GameObject dragonfly = new GameObject("Dragonfly");
dragonfly.AddComponent<FlightBehavior>();
dragonfly.AddComponent<SwimBehavior>(); // Flies AND swims — no hierarchy conflict.
dragonfly.AddComponent<HealthBehavior>();
C++ (Unreal — ActorComponents)
UCLASS()
class UFlightComponent : public UActorComponent { public: void Fly(); };
UCLASS()
class UHealthComponent : public UActorComponent { public: void TakeDamage(int32 Amount); };
// AActor composes behavior from independent UActorComponents, same model as Unity.
AEnemy::AEnemy()
{
FlightComp = CreateDefaultSubobject<UFlightComponent>(TEXT("Flight"));
HealthComp = CreateDefaultSubobject<UHealthComponent>(TEXT("Health"));
}
Unreal Blueprint
Attaching Actor Components in the Blueprint editor's Components panel is
composition made visual — a designer builds an actor's capabilities by
adding components, the same "toolbox" model as C#'s AddComponent<T>().
Practical Example
A UI system needs draggable, resizable, and closeable windows, but not every window needs every capability.
public interface IDraggable { void OnDrag(Vector2 delta); }
public interface IResizable { void OnResize(Vector2 delta); }
public interface ICloseable { void OnClose(); }
public class DraggableBehavior : IDraggable { public void OnDrag(Vector2 delta) { /* ... */ } }
public class ResizableBehavior : IResizable { public void OnResize(Vector2 delta) { /* ... */ } }
public class Window
{
public IDraggable Drag; // Optional — may be null for a fixed-position window.
public IResizable Resize; // Optional — may be null for a fixed-size window.
}
A settings window can compose DraggableBehavior without ResizableBehavior;
a resizable-but-fixed-position debug console composes the opposite — no
inheritance hierarchy has to anticipate every combination in advance.
Common Mistakes
- Reaching for inheritance as the default tool for code reuse. Inheritance is appropriate for a genuine, stable "is-a" relationship that won't need to mix-and-match with unrelated capabilities later; reusing code alone is better served by composition or a shared utility.
- Building deep inheritance chains (four, five, or more levels) "just in case." Each additional level makes the hierarchy more rigid and harder to reason about — a change to a method three levels up can have effects that are difficult to trace down through every descendant.
- Using composition but re-deriving an inheritance-like coupling anyway, e.g. every component reaching directly into every other component's internals — composition's benefit depends on components staying genuinely independent, not just physically separate.
- Treating this as an all-or-nothing choice. Real codebases (including
Unity and Unreal's own engine architecture) mix both: a small, stable
inheritance hierarchy (
MonoBehaviour→Enemy) combined with composed capabilities (attached components) is normal and often the right answer, not a compromise to be avoided.
Game Development Example
An RPG needs character capabilities (melee attack, spellcasting, stealth) to combine in ways that don't map cleanly onto a class hierarchy — a rogue might have stealth and melee, a battlemage might have melee and spellcasting.
public class MeleeAttackComponent : MonoBehaviour { public void Attack() { } }
public class SpellcastComponent : MonoBehaviour { public void CastSpell(string id) { } }
public class StealthComponent : MonoBehaviour { public void EnterStealth() { } }
// Rogue: melee + stealth. Battlemage: melee + spellcasting. Assembled independently.
GameObject rogue = Instantiate(characterPrefab);
rogue.AddComponent<MeleeAttackComponent>();
rogue.AddComponent<StealthComponent>();
Trying to model this with inheritance alone (Rogue : MeleeCharacter,
Battlemage : MeleeCharacter) breaks down the moment a StealthyMage is
needed — composition absorbs that combination with zero hierarchy changes.
Best Practices
- Default to composition for capabilities that might need to combine, change at runtime, or apply to otherwise-unrelated types.
- Reserve inheritance for a genuinely stable "is-a" relationship where every subclass really does share the base's full identity, not just some of its code.
- Keep inheritance chains shallow — two or three levels is a reasonable ceiling before reconsidering the design.
- When unsure, ask "will this type ever need to combine with a capability from a sibling branch of the hierarchy?" — if yes, that capability belongs in a composed component, not the class hierarchy.
Exercises
- Take a hypothetical
Enemy → FlyingEnemy → FlyingArcherEnemythree-level inheritance chain and redesign it using composition (FlightComponent,RangedAttackComponent) attached to a singleEnemybase — describe what became easier to combine. - Identify one capability in a game you know well that appears on otherwise-unrelated object types (e.g. "can be set on fire" applying to both characters and destructible scenery), and design a composed component for it rather than a shared base class.
- You're designing a vehicle system with cars, boats, and planes, where
some vehicles are amphibious. Decide how much of the design should be
inheritance (a shared
Vehiclebase) versus composition (DriveComponent,SailComponent,FlightComponent), and justify the split. - Research the "favor composition over inheritance" principle's origins in the Gang of Four's Design Patterns, and explain in your own words why they still kept inheritance as a tool rather than recommending composition exclusively.
Related Concepts
- Inheritance — the specific mechanism this concept weighs against composition; understanding what inheritance commits you to is necessary before understanding why composition is often preferred.
- Interfaces — composition is frequently implemented through interfaces, letting composed parts be swapped or mocked without depending on a concrete class.
- Polymorphism — both inheritance and composition can achieve polymorphic behavior; this concept is about which structure to build that polymorphism on top of.
Further Reading
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules, which this document follows.- Unity documentation — Unity's Component-based architecture: https://docs.unity3d.com/Manual/class-GameObject.html
- Unreal Engine documentation — Actor Components: https://dev.epicgames.com/documentation/en-us/unreal-engine/components-in-unreal-engine