OOP & Design Principles • Inheritance & CompositionEasy⏱️ ~2 min
Inheritance vs Composition: Core Mechanisms and When Each Makes Sense
Inheritance models an "is a" relationship where a subclass extends a base class, inheriting all non-private members at compile time. When you write class Dog extends Animal, Dog automatically gets all Animal methods and fields. This enables polymorphism: you can substitute a Dog wherever an Animal is expected. Composition models a "has a" relationship where objects gain capabilities by holding references to other objects and delegating to them. A Dog class might contain a BarkingBehavior component and call behavior.bark() when needed.
Inheritance shines when you have a stable type taxonomy with true substitutability. For example, a compiler Abstract Syntax Tree (AST) with sealed node types (Expression, Statement, Declaration) benefits from inheritance because the hierarchy is fixed, nodes truly substitute for their base, and you get exhaustive pattern matching. The behavior is uniform across all nodes, and evolution is controlled by a single team.
Composition excels when behaviors vary independently or need runtime flexibility. At Google and Meta, cross cutting concerns like timeouts, retries, circuit breaking, and tracing are implemented as composable layers around Remote Procedure Call (RPC) clients rather than subclassing clients. When customer facing APIs have p99 latency targets of 300 to 500 milliseconds and allocate only 10 to 50 milliseconds per internal hop, you need per call policy adjustments without changing type hierarchies. Adding a new retry strategy or timeout policy becomes a matter of swapping a component, not modifying a base class used by hundreds of teams.
In practice, production systems combine both: shallow inheritance for type identity and contracts (depth less than or equal to 2 levels), heavy composition for behavior variation. This balances binary stability, lets you evolve safely across teams, and keeps performance predictable by avoiding deep virtual dispatch chains that prevent Just In Time (JIT) compiler inlining.
💡 Key Takeaways
•Inheritance is compile time and couples subclasses to base internals and evolution; composition is runtime and allows swapping components without changing type hierarchies
•Use inheritance for stable taxonomies with true substitutability (Abstract Syntax Tree nodes, minimal abstract resource interfaces); use composition when behaviors vary independently or need per request changes
•Production systems at Google and Meta use shallow inheritance (depth less than or equal to 2) for contracts and heavy composition for behavior to balance binary stability and extensibility
•Composition enables meeting strict latency budgets: with p99 targets of 300 to 500 milliseconds and 10 to 50 milliseconds per hop, you can adjust policies per call without base class changes
•Deep inheritance hierarchies magnify change risk and can cause megamorphic call sites at hot paths, preventing Just In Time compiler inlining and increasing tail latency
📌 Examples
Google Protocol Buffers favor composition (messages contain messages) over inheritance to guarantee forward and backward compatibility across thousands of services processing billions of messages daily
Meta and Google UI stacks use composition of small React components instead of deep widget inheritance to meet 60 frames per second (16.7 milliseconds per frame budget) by enabling partial tree updates
Cross cutting concerns at Amazon and Google (timeouts, retries, circuit breakers, tracing) are composable layers around clients, not subclasses, enabling per call policy tuning on fan out paths