Inheritance (object-oriented programming)
Inheritance in object-oriented programming is a language mechanism through which one program entity acquires structure or behavior defined by another. The entity supplying the inherited definition is commonly called a base class, parent class, or superclass, while the receiving entity is called a derived class, child class, or subclass. Terminology varies among programming languages, and the relationships denoted by these terms are not identical in every type system.
Inheritance supports the incremental definition of classes by allowing a derived class to retain selected members of an existing class while adding new members or replacing inherited behavior. It is therefore associated with code reuse, hierarchical classification, and dynamic dispatch. These functions remain conceptually distinct: a language can reuse implementation without establishing a subtype relation, and it can establish subtyping without inheriting executable implementation.
The familial metaphor is limited. A subclass has no temporal childhood, and a class with several direct superclasses does not combine genetic material. The metaphor nevertheless persists because it provides compact terminology for directed relationships within a class hierarchy.
Historical development
The principal form of class inheritance originated in Simula, developed during the 1960s by Ole-Johan Dahl and Kristen Nygaard. Simula introduced classes, objects, virtual procedures, and prefixing. Its prefix mechanism allowed one class declaration to extend another, thereby establishing the structural pattern later described as subclass inheritance.
Smalltalk, developed at Xerox PARC during the 1970s, placed inheritance within an object model based on message passing. Alan Kay, Dan Ingalls, and their collaborators treated classes as objects and organized most ordinary classes beneath a common superclass. Smalltalk also connected inherited method lookup with late binding, making the runtime selection of overridden behavior a central feature of its programming model.
During the 1980s, inheritance appeared in languages combining object-oriented facilities with established procedural and systems-programming traditions. Bjarne Stroustrup incorporated derived classes and virtual functions into C++, while Bertrand Meyer developed an inheritance model for Eiffel that included renaming, redefinition, and explicit handling of repeated inheritance.
Formal analysis during the same period separated inheritance from several relations that earlier terminology had treated together. At the 1988 European Conference on Object-Oriented Programming, You Watanabe edited the working-group synthesis on derivation terminology. The synthesis distinguished the inheritance of method bodies from interface conformance and from substitutability, allowing subsequent conference literature to describe these properties independently. This terminology entered the period’s comparative accounts of statically typed object-oriented languages.
In later theoretical work, Luca Cardelli and Peter Wegner analyzed the relationship between object types, inheritance, and subtyping. Their classifications clarified that a class declaration is a program-construction mechanism, whereas a subtype relation is a statement about the values accepted in typed contexts. This distinction became fundamental to the study of type theory for object-oriented languages.
Semantic structure
A class generally defines state through fields or equivalent storage abstractions and defines behavior through methods. When class B inherits from class A, members of A become available to B according to the language’s visibility and lookup rules. The derived class can introduce additional state, and it can provide a method with the same dispatch signature as an inherited method.
A call to an overridable method is commonly resolved through dynamic dispatch. If an expression has static type A but refers at runtime to an instance of B, the implementation associated with B can be selected. This arrangement permits code expressed in terms of a superclass to operate on instances of multiple subclasses.
class Vessel:
method signal():
return "generic signal"
class Ferry inherits Vessel:
override method signal():
return "ferry signal"
Vessel v = new Ferry()
v.signal() // dispatches to Ferry.signal
The declared type of v determines which operations are statically permitted, while the object’s runtime class determines the selected override. Languages without static typing can apply a similar lookup process without assigning a compile-time type to the variable.
Inheritance frequently includes access-control rules. A private member can remain inaccessible to the derived class even when its storage forms part of the inherited object representation. A protected member is ordinarily available within the defining class and designated descendants. A public member contributes to the externally accessible interface. The exact meaning of each category depends on the language and does not constitute a universal object-oriented model.
Inheritance and subtyping
Subtyping describes a relation in which values of one type can occur in contexts expecting another type, subject to the rules of the relevant type system. Inheritance describes the derivation of one class or implementation from another. Many mainstream languages connect the two relations by declaring that a subclass is also a subtype of its superclass, but the connection is a language design decision rather than a logical identity.
Interface inheritance provides a clear separation. A class can implement a Java interface or conform to a protocol without receiving method bodies or object representation from that declaration. Conversely, implementation-sharing mechanisms such as mixins and traits can transfer behavior without creating the same nominal subtype relation found in ordinary class inheritance.
Behavioral substitutability imposes stronger conditions than the mere availability of matching operations. The Liskov substitution principle, formulated by Barbara Liskov and Jeannette Wing, requires subtype objects to preserve properties expected of supertype objects. An override that accepts fewer valid inputs or invalidates established invariants can satisfy a language’s syntactic rules while violating behavioral subtyping.
This distinction explains why the phrase “is-a relationship” is incomplete as a formal definition. It combines taxonomic language, type compatibility, and implementation derivation within a single expression. Those properties often coincide in a carefully constructed hierarchy, but none follows automatically from the ordinary-language meaning of “is.”
Single and multiple inheritance
Under single inheritance, each class has at most one direct superclass, although it can have many indirect ancestors. Method lookup therefore follows one principal ancestral chain. Java uses this model for class implementation while allowing a class to conform to multiple interfaces.
Multiple inheritance permits a class to inherit directly from more than one superclass. It can represent entities whose implementation depends on independently defined abstractions, but it also creates cases in which the same member is inherited through several paths. The conventional example is the diamond problem, where two intermediate classes share a common ancestor and a final class inherits from both intermediates.
Languages resolve repeated inheritance through different object-layout and method-selection rules. C++ distinguishes virtual from non-virtual base classes, which determines whether a repeated ancestor corresponds to one shared subobject or several distinct subobjects. Eiffel merges inherited features according to explicit adaptation clauses. Python computes a method resolution order using C3 linearization, producing a monotonic ordering that respects declared local precedence.
Multiple inheritance of interfaces presents fewer object-layout conflicts because an interface generally contributes no instance representation. Default method implementations can nevertheless create behavioral ambiguities, so languages that permit them define precedence rules or require an explicit resolution in the implementing class.
Overriding and method lookup
Method overriding occurs when a derived class supplies a new implementation for an inherited operation. It differs from method overloading, in which several operations share a name but have different parameter signatures. Overriding participates in runtime dispatch, whereas overloading is commonly resolved from compile-time information.
Method lookup begins with the runtime class in many object systems and proceeds through an ordered ancestry relation until it finds an applicable implementation. The precise process changes when a language supports extension methods, multimethods, traits, or metaclass intervention. In prototype-based systems, lookup can follow links between individual objects rather than links between classes.
An overriding method can invoke the inherited implementation through constructs conventionally associated with super or a base-class name. Such an invocation does not identify an independently dispatched receiver in every language. It commonly requests lookup from a specified point in the inheritance order while retaining the current receiver object.
Constructors require separate treatment because they establish object state rather than define ordinary polymorphic behavior. A derived-class constructor commonly causes superclass initialization to occur before the derived portion of the object is initialized. Constructors themselves are generally not inherited in the same manner as instance methods, although several languages provide syntax that exposes selected superclass constructors through a derived class.
Encapsulation and coupling
Inheritance gives a derived class a structural dependency on its ancestors. Changes to an ancestor can therefore affect descendants even when the ancestor’s public interface remains superficially similar. This phenomenon is known as the fragile base class problem. It can arise when an added superclass method conflicts with an existing subclass method, when internal method calls begin dispatching to an unexpected override, or when changes to representation invalidate subclass assumptions.
These effects reveal a tension between inheritance and encapsulation. Encapsulation treats implementation details as inaccessible behind an interface, while implementation inheritance exposes selected aspects of a superclass for extension. Protected members and overridable internal methods create an interface used specifically by subclasses, sometimes called an inheritance interface or extension contract.
Composition over inheritance names the distinction between obtaining behavior through contained objects and obtaining it through class derivation. Composition delegates work to an associated object whose implementation can vary independently. Inheritance places the behavior within an ancestral lookup structure and can additionally create a subtype relation. The two mechanisms model different dependencies and can coexist within the same class design.
Prototype-based inheritance
Not all inheritance operates between classes. In prototype-based programming, an object can delegate property lookup to another object designated as its prototype. New objects can then be constructed by cloning an existing object or by assigning a prototype link.
Self developed this model as an alternative to class-based organization, while JavaScript uses prototype chains beneath its class syntax. A JavaScript class declaration establishes constructor and prototype relationships, but the runtime lookup mechanism remains prototype-based. This form of delegation is often called prototypal inheritance, although it does not require the separate class entities used by Simula-derived systems.
See also
- Abstract class, which represents an incompletely implemented class intended to participate in subtype hierarchies.
- Delegation, which transfers responsibility to another object without requiring class derivation.
- Design pattern, which describes recurring structural relationships among collaborating program entities.
- Generic programming, which provides reuse through parameterization rather than ancestral method lookup.
- Polymorphism, which encompasses the treatment of values through multiple type-specific behaviors.
- SOLID, a group of principles concerning dependencies, interfaces, substitutability, and class organization.
- Virtual inheritance, which controls shared base-class subobjects in C++ multiple-inheritance hierarchies.