Inheritance is the mechanism in object-oriented programming by which a subclass derives the fields and methods of a superclass, reusing its implementation and extending or overriding parts of it. The relationship it models is is-a: a SavingsAccount is an Account, so it can be used anywhere an Account is expected.
Inheritance combines two things that are often confused: reuse of implementation and creation of a subtype. It enables polymorphism through overriding, but it also creates the tightest form of coupling between classes, which is why modern design guidance treats it as a precise tool rather than a default way to share code.
How inheritance works at runtime
When a class inherits from another, every object of the subclass contains the state of the superclass plus its own additional fields. Methods that the subclass does not redefine are looked up in the superclass, while methods it overrides replace the parent behavior for objects of the subclass.
In statically typed languages such as Java, C# and C++, overridable methods are dispatched through a virtual method table, a per-class array of function pointers that the runtime consults to call the correct implementation. Dynamic languages such as Python and Ruby walk a method resolution order, the ordered list of classes searched for an attribute. In both cases the call site does not need to know the concrete class, which is the core of runtime polymorphism.
Constructors are not inherited in most languages; instead a subclass constructor must initialize the parent part first, explicitly through super(...) or implicitly through a default constructor. Calling overridable methods from a constructor is a known hazard, because the subclass state is not yet initialized when the parent constructor runs.
Forms of inheritance
Languages differ in which inheritance forms they allow, mainly because multiple inheritance of implementation creates ambiguity. The table summarizes the common forms and how mainstream languages handle them.
| Form | Structure | Language support | Main concern |
|---|---|---|---|
| Single inheritance | One parent per class | Java, C#, Kotlin, Swift classes | Deep chains become rigid |
| Multiple inheritance | Several parent classes | C++, Python | Diamond problem and name clashes |
| Interface inheritance | Inherits contracts, not code | Java, C#, Go implicitly | Implementation must be repeated or delegated |
| Mixins and traits | Reusable method bundles | Ruby modules, Scala and Rust traits, PHP traits | Ordering and conflict rules |
| Prototypal inheritance | Objects delegate to other objects | JavaScript | Shared mutable prototype state |
The diamond problem arises when a class inherits from two classes that share a common ancestor: it becomes unclear which version of an inherited method applies. C++ resolves it with virtual inheritance, while Python uses the deterministic C3 linearization to order the classes. Languages such as Java avoid the issue for state by permitting multiple inheritance only of interfaces.
Inheritance among related object-oriented concepts
Inheritance is one of several mechanisms for relating classes, and choosing well requires seeing what each mechanism actually shares. The comparison below positions it against its closest neighbors.
| Mechanism | What is shared | Relationship | Coupling |
|---|---|---|---|
| Inheritance | Implementation and type | Is-a | High |
| Interface implementation | Type contract only | Can-do | Low |
| Composition | Behavior through a held object | Has-a | Low to moderate |
| Delegation | Calls forwarded to a helper | Uses-a | Low |
| Generics | Algorithm over many types | Works-with | Low |
The guideline favor composition over inheritance expresses a practical observation: composition lets behavior be swapped at runtime and keeps classes independent, while inheritance fixes the relationship at compile time. Inheritance remains the right choice when a genuine subtype relationship exists and the parent class was designed for extension.
The Liskov substitution principle
Correct inheritance obeys the Liskov substitution principle: any code written against the superclass must work unchanged with any subclass. A subclass may weaken preconditions and strengthen postconditions, but it must not demand more from callers or promise less to them.
The classic violation is a Square that inherits from Rectangle. Mathematically a square is a rectangle, yet setting the width of a mutable square must also change its height, which breaks callers that expect width and height to vary independently. The lesson is that inheritance should follow behavioral compatibility, not only real-world taxonomy.
When to use inheritance and when to avoid it
Inheritance works best for shallow, stable hierarchies in which the parent class defines a clear extension point. The decision table reflects common practice in professional codebases.
| Situation | Recommended approach | Reason |
|---|---|---|
| Framework defines a base class with hooks | Inherit and override hooks | Designed extension points |
| Several classes need one shared helper | Composition | Avoids an artificial is-a link |
| Unrelated types need common behavior | Interface plus delegation | Keeps hierarchies independent |
| Algorithm skeleton with varying steps | Template method pattern | Parent controls the sequence |
| Third-party class not built for extension | Wrap it instead | Avoids the fragile base class problem |
The fragile base class problem is the main risk: a harmless-looking change in a parent, such as calling one public method from another, can silently break subclasses that override those methods. Hierarchies deeper than two or three levels also make behavior hard to trace. Languages respond with explicit controls, such as final in Java, sealed in C# and Kotlin, and classes that are closed by default in Kotlin.
A plain-language summary
Inheritance lets a programmer say that one kind of thing is a special version of another. If the program already knows what a vehicle can do, it can describe a truck by saying it is a vehicle plus a cargo bed, instead of writing everything again.
The benefit is reuse, but there is a catch. The truck now depends on every detail of the vehicle, so a change to the vehicle can affect the truck in surprising ways. Inheritance is powerful when the family relationship is real and risky when it is used only to borrow code.
An example from practice
A logistics company builds a shipment tracking system with an abstract Carrier class. It defines a fixed workflow, which authenticates, requests a label and parses tracking events, and leaves three abstract methods for each carrier to implement. Separate subclasses for a national postal service and two courier companies override only those three hooks.
When a new courier is added, a developer writes one subclass of about 200 lines, and the rest of the system uses it through the Carrier type without changes. Logging and retry logic, which every carrier needs, are injected through composition rather than added to the base class. The result is a stable two-level hierarchy that has absorbed new carriers for years without base class rewrites.