OOP

Composition vs Inheritance

Two ways to reuse code and build complex types: inheriting from a base class ("is-a") or composing objects from other objects ("has-a"). The community mantra: favor composition.

Is-a vs has-a.

Inheritance models "is-a": a Car is a Vehicle. Composition models "has-a": a Car has an Engine. With composition you build behavior by holding other objects and delegating work to them.

bad.py
# Problem: inheritance used for "has-a" — awkward and brittle.
class Engine:
    def start(self): return "vroom"

class Car(Engine):                # Car is-NOT-an Engine
    pass
# Now Car IS an Engine in the type system — wrong model.
# Want electric + gas? Inheritance trees explode.
good.py
# Fix: composition — Car HAS an Engine and delegates to it.
class Engine:
    def start(self): return "vroom"

class ElectricMotor:
    def start(self): return "hum"

class Car:
    def __init__(self, powertrain):
        # Inject whatever "starts" — gas, electric, mock in tests.
        self.powertrain = powertrain

    def start(self):
        return self.powertrain.start()  # delegate, don't inherit

print(Car(Engine()).start())          # "vroom"
print(Car(ElectricMotor()).start())   # "hum"

Why favor composition.

  • Flexibility — swap composed parts at runtime; combine behaviors freely.
  • Looser coupling — depend on a part's interface, not a base class's internals.
  • No fragile base class — you're insulated from parent implementation changes.
  • Avoids class explosion — mix features instead of a subclass per combination.
Still use inheritance For genuine, stable is-a relationships and to share a common interface/contract. It's a tool, not a taboo.

Delegation and patterns.

  • Delegation — composition's mechanism: forward calls to the held object.
  • Patterns built on composition — Strategy, Decorator, Bridge, Adapter.
  • Cost — more objects/wiring, sometimes boilerplate forwarding methods.
  • Inheritance's edge — less boilerplate for pure specialization and shared code when is-a truly holds.

Interview Questions

Filter

Building a class by including instances of other classes as fields and delegating work to them — a "has-a" relationship. The composed object is made up of its parts.

Composition models "has-a" (a Car has an Engine); inheritance models "is-a" (a Car is a Vehicle). Choosing correctly starts with which relationship truly describes the domain.

A design guideline (from the GoF) advising you to reach for composition/delegation to reuse and combine behavior by default, using inheritance only when a genuine, stable is-a relationship exists — because composition is more flexible and less tightly coupled.

A Car has an Engine and Wheels; a House has Rooms; an Order has LineItems. Each whole is composed of parts it holds and uses.

When an object handles a request by forwarding it to another object it holds (its component). Delegation is the mechanism composition uses to reuse behavior — the outer object exposes a method that simply calls the inner object's method.

No. Inheritance is valuable for true is-a relationships, for sharing a common interface/contract (enabling polymorphism), and for frameworks' extension points. The guidance is to avoid using it merely for code reuse when has-a fits better — not to eliminate it.

A looser form of composition (a "has-a" where the part can exist independently of the whole and may be shared). E.g. a Team has Players, but players outlive the team. Strict composition implies the part's lifetime is owned by the whole (destroying the whole destroys the parts).

Composed parts can be chosen and swapped at runtime (inject a different strategy/engine), behaviors can be combined freely (mix multiple components) avoiding a subclass per combination, and you depend only on the part's interface rather than a base class's implementation, so you're insulated from its internal changes. Inheritance fixes the relationship at compile time, allows only one parent, and couples you to the base's implementation details.

With composition you interact with a component only through its public interface, not by inheriting and depending on its implementation internals. So changes inside the component don't silently alter your class's behavior the way base-class changes can affect subclasses. The coupling is limited to the (stable) interface rather than the (mutable) implementation, making the design more robust to change.

Adding coffee options via inheritance leads to CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, … — combinatorial explosion. With composition (the Decorator pattern), you wrap a base Coffee with MilkDecorator, SugarDecorator in any combination at runtime, no new subclass per combo. Similarly, needing a class to be both "Serializable-behaving" and "Loggable-behaving" is easy by composing those components but awkward with single inheritance.

Both are "has-a," but composition implies strong ownership: the part's lifecycle is bound to the whole (a House owns its Rooms — destroy the house and the rooms go with it), and parts usually aren't shared. Aggregation is weaker: the part can exist independently and be shared (a Department has Employees who exist without it). The distinction matters for ownership, cascading deletes, and whether references are shared.

Ask: is it truly an "is-a" that will remain valid, and does the subtype satisfy the base's full contract (LSP)? If yes and you want to share interface/behavior, inheritance may fit. If the relationship is "has-a," you only want to reuse some behavior, you need to combine multiple behaviors, or you want runtime flexibility and loose coupling, choose composition/delegation. A useful test: would the subclass want to not inherit some parent members, or does "is-a" feel forced? Then prefer composition.

Many: Strategy (inject an algorithm object), Decorator (wrap to add behavior), Bridge (separate abstraction from implementation), Adapter (wrap an incompatible interface), Composite (tree of parts), Proxy, and dependency injection generally. These achieve flexible, combinable behavior via has-a relationships instead of rigid inheritance hierarchies.

Composition often requires writing forwarding/delegation methods that just call through to the component, which can be verbose compared to inheriting them for free. Mitigations: language support like Kotlin's by delegation, C#/Lombok delegation helpers, or generating delegate methods; exposing the component directly when appropriate; or keeping the delegating interface small. The small boilerplate cost is usually worth the flexibility and decoupling gained.

Instead of subclassing to vary an algorithm (e.g. different sorting or pricing per subclass), you define a Strategy interface for the varying behavior and have the context hold a reference to a strategy object, delegating to it. Concrete strategies implement the interface, and you inject the desired one — even changing it at runtime. This decouples the algorithm from the context, avoids a subclass per variation, allows reuse of strategies across contexts, and makes each algorithm independently testable. It's the textbook example of "favor composition": behavior becomes a plugged-in object rather than a fixed part of a class hierarchy.

The diamond problem arises with multiple implementation inheritance: a class inheriting from two bases that share a common ancestor faces ambiguity over which inherited state/behavior applies. Composition sidesteps it by having the class hold instances of the needed types and delegate explicitly, so there's no ambiguous inherited state — you decide exactly which component handles which call. This is a key reason languages that forbid multiple class inheritance still function well: you compose multiple collaborators instead of inheriting from multiple bases, controlling behavior precisely.

Yes, and it's common: use interface inheritance for the type/contract and composition for implementation reuse. For example, a class implements a List interface (subtyping, for polymorphism) but internally composes an existing list and delegates to it, adding behavior — this is exactly how the Decorator and Adapter patterns work, and how "wrapper" collections are built safely instead of extending a concrete collection (which is fragile). So you inherit the interface to be substitutable, and compose to reuse behavior without the coupling of implementation inheritance.

When there's a true, stable is-a relationship and you want subtypes to be polymorphically substitutable through a shared type; when a framework defines extension via subclassing (template methods, lifecycle hooks); when the base and derived are in the same codebase/module so the fragile-base-class risk is controlled; and when composition would require large amounts of pure pass-through delegation for no added value. Inheritance also naturally expresses classification hierarchies (ASTs, exception hierarchies) and can be clearer and less boilerplate-heavy there. The key is that the relationship is semantically "is-a" and honors LSP, not merely a convenient way to grab some code.

Composition improves testability: because collaborators are injected (typically as interfaces), you can substitute test doubles (mocks/fakes) to isolate the unit, control dependencies, and verify interactions — without touching production wiring. With implementation inheritance, the reused behavior is baked into the class and can't be swapped out for a test, and overriding parent methods to fake behavior is brittle and couples tests to the hierarchy. So composition provides clean seams for testing, which is a major practical reason to prefer it, especially at boundaries (I/O, external services).

Identify the behavior currently obtained via the base class. Extract that behavior into its own class with a clear interface. In the former subclass, add a field holding an instance of that new component and replace inherited calls with delegation to it. Update construction to inject the component (dependency injection), enabling substitution. Remove the extends relationship, keeping an implemented interface if polymorphism is still needed for callers. Run tests at each step to ensure behavior is unchanged. The result: the class now has the behavior via a swappable component rather than is a subclass, reducing coupling and enabling runtime flexibility. Do it incrementally, one collaborator at a time, behind a stable public interface.

Inheritance creates white-box reuse: the subclass sees and depends on the base's internals (protected members, call order, overridable methods), so changes to the base can propagate unexpectedly to all subclasses — high coupling, wide change radius. Composition is black-box reuse: you depend only on a component's published interface, so internal changes to the component don't leak, and the change radius is contained to the interface contract. This means composition-based designs tend to localize the impact of changes and are easier to evolve, while deep inheritance hierarchies can turn a small base-class edit into a system-wide regression risk.

A guideline/heuristic, not an absolute rule. It nudges you to default to composition for reuse and flexibility, but inheritance remains the right tool for genuine is-a relationships and shared contracts. Treat it as "prefer composition unless inheritance clearly fits," not "never inherit."

White-box reuse is inheritance: the subclass can see and depend on the base's internals. Black-box reuse is composition: you reuse a component only through its public interface, without seeing its internals. Black-box reuse is more encapsulated and robust to change.

Yes, routinely. A class might extend a base class (or implement an interface) for its type/contract while composing helper objects for its behavior. Composition and inheritance aren't mutually exclusive — good designs mix them appropriately.

Composition lets you split responsibilities into separate, focused collaborator objects that a class holds and delegates to, rather than piling everything into one base/subclass hierarchy. Each component has one job (high cohesion), and the composing class coordinates them. This keeps classes small and single-purpose, whereas inheritance tends to accumulate responsibilities down the hierarchy.

Extending a concrete collection (e.g. subclassing ArrayList to add logging) is fragile: overriding one method may miss others that internally bypass it, and base-class changes can break you. The wrapper approach composes the collection behind the same interface and delegates, intercepting calls consistently. This black-box reuse is robust to the collection's internal changes and follows "favor composition." Effective Java's item on "favor composition over inheritance" uses exactly this example.

Composition. Because the behavior lives in a swappable component (often behind an interface), you can change it at runtime by injecting a different implementation — e.g. switch a sorting or pricing strategy on the fly. Inheritance fixes behavior at object-creation time based on the concrete subclass; you can't change an object's class after construction, so varying behavior requires creating a different object. This runtime flexibility is a major reason patterns like Strategy use composition.

Decorator wraps an object in another object implementing the same interface, adding behavior and delegating the rest. You can stack decorators in any order and combination at runtime (e.g. buffered + encrypted + compressed streams), so N independent features need N decorators, not 2^N subclasses. Inheritance would require a subclass for every combination (class explosion) and fix them at compile time. Decorator keeps each feature cohesive and combinable, is open for extension (add a new decorator) without modifying existing code, and avoids touching the core class — a clear win for orthogonal, composable features.

Composition can introduce many small objects and wiring, delegation boilerplate (forwarding methods), and more indirection that can make simple flows harder to trace ("where does this actually happen?"). Excessive decomposition into tiny collaborators plus heavy dependency injection can produce configuration-heavy, fragmented designs that are hard to follow. There's also a minor runtime cost (extra objects/indirection). The remedy is balance: compose where variation, testability, or flexibility justify it; don't shatter a cohesive, stable concept into needless parts. Favoring composition doesn't mean maximizing it.

Inheritance models this poorly because an object can't change its class at runtime — you'd have to destroy the Employee and create a Manager, losing identity and references. Composition handles it cleanly: model the varying aspect as a role/state object that the person has and can swap (Strategy/State pattern), e.g. person.setRole(new ManagerRole()), or compose multiple role objects a person can gain/lose. This keeps the person's identity stable while behavior/attributes change, supports having multiple roles simultaneously, and avoids the rigidity of a fixed subclass. It's a classic case where "is-a" is actually a mutable "plays-the-role-of," best expressed with composition.