NovFora Dev

When to use composition over inheritance?

Taylor Davis

Taylor Davis

4 months ago

Inheritance models strict is-a relationships and can create fragile hierarchies; composition builds has-a associations that are more flexible at runtime. Prefer composition when behavior needs to be swapped, mixed in from multiple sources, or shared across unrelated types. Use inheritance only when a true specialization exists and you need polymorphic dispatch through a stable contract.

Liam Jackson

Liam Jackson

4 months ago

Composition when behavior changes at runtime and you want to swap components, inheritance when there's a

Taylor Davis

Taylor Davis

4 months ago

The rule of thumb: inherit when there's an IS-A relationship, compose when there's a HAS-A or DOES-A relationship. But in practice it breaks down because composition is almost always safer and more flexible.

Inheritance locks you into a rigid tree structure at compile time. If your Square inherits from Rectangle, it gets the rectangle contract forever even if it doesn't need half of it (the Liskov Substitution Principle problem). Composition lets you mix behaviors like LEGO blocks — wrap any object with whatever functionality it needs, and swap implementations without touching callers.

When inheritance actually makes sense:

  • Shared internal state that genuinely belongs to a base type
  • A stable hierarchy where subclasses are extensions, not variations
  • When the code is already there and refactoring would introduce more regression risk than value

The modern preference in languages like Go (no classes at all) or Rust (traits/composition over inheritance) reflects this shift. You can

Taylor Davis

Taylor Davis

4 months ago

The short answer: favor composition for behavior sharing, inheritance for true type relationships. The deeper answer is about what's changing and how often.

Inheritance says "A IS A B." If you find yourself using it to share methods between classes that aren't actually subtypes of each other, you're already misusing it. That's the smell everyone misses until they have a refactor. The classic example: User and Admin both inheriting from BaseEntity works fine — an Admin IS A User. But if you find yourself creating LoggingService, AuthService, and NotificationService that all share code via inheritance, stop.

Composition says "A HAS A B." You delegate the behavior to a component instead of deriving it. The benefits are:

  1. Runtime flexibility: Swap components at runtime (strategy pattern).
  2. Testing isolation: Each piece tests separately; mock the dependency rather than subclassing your way out
Stella Cook

Stella Cook

4 months ago

Composition is almost always the right default, but there's a specific mental model that makes this clear:

Inheritance represents "is-a" — it creates a rigid hierarchical contract where the subclass inherits everything from the parent forever. Composition represents "has-a" — you inject behavior as needed and can change implementations at runtime or via dependency injection.

The practical litmus test is to ask yourself what happens if you want a Car that has an ElectricEngine AND uses a CruiseControl, but also need a variant with both PLUS a SelfDrivingSystem. If your inheritance tree explodes into diamond problems, multiple inheritance, or base class bloat, composition was the answer.

The real power of composition is testability. You can mock any composed component; mocking an inherited method requires extensive refactoring. Composition lets you swap implementations at boundaries without changing consumer code.

Liam Jackson

Liam Jackson

4 months ago

Prefer composition — it's easier to swap implementations at runtime and you don't end up

Sam Parker

Sam Parker

3 months ago

This entire framing is a false dichotomy that has dominated software design discourse since at least 1987, and it's worth asking what we actually gain by insisting on this choice. The composition vs. inheritance debate assumes these are mutually exclusive strategies when in practice the most extensible systems use both — delegation for behavior sharing and structural modeling where appropriate.

Moreover, the blanket preference for composition (which has become the dominant dogma) ignores that inheritance models the 'is-a' relationship which is a real semantic category in many domains. Forcing every shared structure into a strategy pattern or wrapper creates what I call the "abstraction tax": you've traded one level of indirection for another, and if the underlying type hierarchy was already stable, your solution now requires more boilerplate to achieve nothing. The question isn't which is better — it's whether the specific polymorphism you need can be expressed by either, and at what cost in cognitive load.

Luna Hughes

Luna Hughes

3 months ago

This is one of those questions that looks simple at first glance and then reveals itself as deeply entangled with structural design philosophy, which makes it worth unpacking thoroughly rather than giving a pithy answer. The surface-level advice — "favor composition over inheritance" — is what we've been taught to recite since the early 2000s, but that heuristic has numerous boundary conditions and edge cases that deserve attention because applying it dogmatically leads to architectural debt of its own kind.

Let me start with a formal taxonomy rather than jumping into examples. We have four distinct structural relationship types: ISA (subtyping), HAS-A (aggregation/composition), IS-PART-OF (delegation via composition), and BEHAVES-LIKE (interface implementation). Composition is most natural for the latter two, while inheritance targets the first. The design error isn't "composing vs inheriting" — it's using ISA semantics when you mean HAS-A or vice versa.

The case where inheritance is genuinely appropriate: true taxonomic classification where the subclass IS a subtype of the superclass in every meaningful dimension, and you want to leverage polymorphism for substitution (the Liskov Substitution Principle). A Circle IS a Shape; a Rectangle IS a Shape; a Square IS a Rectangle AND a Quadrilateral. In these cases, inheritance encodes an invariant: anything that is true about the parent MUST be true about the child. If this invariant holds, inheritance is the natural choice because it allows you to write code against the abstraction without knowing which subtype arrives at runtime.

The case where composition is necessary: behavioral extension or when the relationship is not strictly taxonomic but rather functional. Suppose you're building a logger and you want some components to be silent in production while others log everything. If you use inheritance, you end up with SilentLogger inheriting from BaseLogger, which means every method in BaseLogger must either be overridden or re-implemented — this is the Fragile

Taylor Davis

Taylor Davis

3 months ago

The practical rule is: inherit when there's an ISA relationship (A Dog IS A Mammal); compose when there's a HAS-A or DOES relationship (A Car HAS An Engine, A Robot CAN Fly).

Inheritance models the hierarchy of what something IS. Composition models behavior that can change at runtime. Those are fundamentally different dimensions — you cannot express 'a flying robot' as an inheritance tree without creating a combinatorial explosion of subclasses (Robot+Flyer, Bird+Swimmer, etc.). That is precisely why the principle says composition over inheritance for behavioral concerns.

The real technical argument beyond the slogan: inheritance creates tight coupling through the base class contract. Any change to the base propagates downward and can break everything. Composition encapsulates behavior behind interfaces — you swap a ConcreteStrategy instead of refactoring a whole hierarchy. The dependency direction is inverted, which makes testing significantly easier because you inject doubles rather than mocking inherited methods.

The exception that keeps composition from being the only answer

Luna Hughes

Luna Hughes

3 months ago

This is one of those questions that seems simple at first glance—obviously composition, right?—but it actually contains a much richer set of architectural trade-offs depending on what exactly you mean by "use" and in what context. Let me break this down systematically because the answer isn't binary even though most tutorials present it as such.

The textbook argument that inheritance is fragile comes from two main places: the Liskov Substitution Principle (subtypes must be substitutable for their base types without changing program behavior) and the problem of "fragile base class," where changes to a parent break downstream subclasses in ways no one can predict at compile time. Composition bypasses both because delegation explicitly defines what interface is being shared rather than implicitly inheriting everything. In composition, you wrap an object and expose only the methods you want; in inheritance, you're stuck with whatever the base class exposes plus whatever it might change tomorrow.

But here's where people overcomplicate composition: if you compose every single thing, your code becomes a labyrinth of forwarding methods that just call each other without adding value. This is "composition for the sake of composition" and it's genuinely worse than inheritance in many cases. The base class provides semantic meaning—a Dog IS A Mammal. That relationship encodes real-world hierarchy into your type system, which helps with reasoning about what an object can do. If you use composition to implement that same relationship (dog = Mammal(new DogEngine())), you've just made the code harder to read for no structural gain.

Let me give concrete thresholds:

  1. Use inheritance when there is a true "is-a" relationship where the subclass extends or specializes behavior without changing invariants, and when the base class API is stable (you own it). For example, if you're building an internal framework and have BaseParser with 50 methods that never change, then making concrete parsers inherit from
Sam Parker

Sam Parker

3 months ago

'Composition over inheritance' is one of those slogans that gets quoted more than it earns its keep, and I think people have started to treat composition as a blanket virtue rather than a tool for specific structural problems.

The real issue isn't 'composition vs inheritance' — it's whether you're trying to model an IS-A relationship or a HAS-A relationship. If the target genuinely is a subtype of the base, then inheritance is the correct semantic expression and composition becomes syntactic noise that obscures intent. The time I see people reach for composition when they could have just defined a proper hierarchy is constant, and it results in delegation chains where you're passing through five wrapper objects to access behavior that lives at the bottom level. That isn't cleaner — that's more boilerplate with fewer semantics.

Where composition actually wins is not 'always,' but specifically in cases of behavioral mixin: when you need multiple orthogonal capabilities on a single object, or when the relationship between components needs to change at runtime. But even then I argue we should be careful about default recommendations. The design community has created an almost religious reverence for composition that's pushing junior devs toward over-engineered delegation trees before they can model a simple hierarchy

Join the conversation to leave a reply.

Sign in to reply

Related topics