Both composition and inheritance are ways to build classes out of other classes, but they capture fundamentally different relationships. Inheritance models an is-a relationship, as in Dog is an Animal, while composition models a has-a relationship, as in Car has an Engine. The widely cited principle to favor composition over inheritance reflects the fact that composition provides greater flexibility and avoids many of the pitfalls of inheritance hierarchies.
Composition is generally preferred for several reasons. It produces loose coupling, allowing components to be swapped at runtime. It sidesteps the fragile base class problem, where seemingly safe changes to a parent class unintentionally break subclasses. It offers greater flexibility by allowing a class to combine behaviors from multiple sources, and it avoids the deep, hard-to-understand hierarchies that complex inheritance trees tend to create. Inheritance remains the right choice only when a true is-a relationship genuinely exists and the behavioral contract is well understood.
Several modern mechanisms exist for code reuse that sidestep the limitations of single inheritance. Mixins are classes that provide reusable methods to other classes without serving as a standalone base, exemplified in Python by a JsonMixin that adds to_json behavior to any class that mixes it in. Traits are similar reusable sets of methods used in languages like Scala, PHP, and Rust, with explicit mechanisms for resolving conflicts when multiple traits provide the same method. Both approaches promote code reuse without forcing deep inheritance hierarchies.
Multiple inheritance, the ability for a class to inherit from more than one parent class, is supported in languages like C++ and Python but disallowed in Java and C#. Its main risk is the diamond problem, which arises when a class inherits from two classes that share a common ancestor, creating ambiguity about which version of an inherited method should be used. Languages solve this problem in different ways: Python uses C3 linearization through its method resolution order, C++ offers virtual inheritance, and Java sidesteps the issue entirely by disallowing multiple class inheritance and relying on interfaces instead. The fragile base class problem, where changes to a base class unexpectedly break subclasses, is another reason to favor composition, mark classes as final when not designed for extension, and document which methods are safe to override.