Skip to content
— CH. 1 · INTRODUCTION —

Inheritance (object-oriented programming)

11 min listen · Ch. 1 of 7
7 sections
  • Inheritance in object-oriented programming begins with two Norwegians in 1967. Ole-Johan Dahl and Kristen Nygaard were working on a language design that would let programmers specify objects belonging to different classes while still sharing common properties. Their solution was to collect those shared properties into a superclass. Each superclass could itself potentially have a superclass, and from that simple idea, the entire modern edifice of class hierarchies was born.

    The concept they helped pioneer first took formal shape in Simula 67, the programming language that put the idea on the map. From there it spread to Smalltalk, then C++, then Java, Python, and dozens of others. Today, inheritance is one of the defining mechanisms of how software is organized across most of the world's major programming languages.

    But inheritance has never been simple. Tony Hoare had planted an early seed as far back as 1966, when he presented remarks on records and the notion of record subclasses. And ever since, programmers have argued about when inheritance helps and when it quietly undermines the very systems it was meant to organize. The story of inheritance is also the story of those arguments.

  • Tony Hoare's 1966 remarks on record subclasses introduced the notion of types with common properties distinguished by a variant tag, with some fields kept private to each variant. Ole-Johan Dahl and Kristen Nygaard built on that foundation a year later, giving their design a key structural property: the values of a subclass would be compound objects, made up of prefix parts belonging to various superclasses plus a main part belonging to the subclass itself. All those parts were concatenated together. Attributes of such a compound object could be read using dot notation.

    Simula 67 was the language that first carried this idea into practice. The language gave programmers a concrete syntax for expressing that one class descended from another, and for accessing the properties the child class acquired from its parent. The innovation was not just conceptual; it was architectural. By arranging classes into hierarchies, programmers could describe the world in terms of shared structure, with each level of the hierarchy adding or refining specifics.

    Mathematically, what inheritance creates is a strict partial order on the set of classes in any given system. That formal property has real consequences: it means the relationships between classes are directional and acyclic, forming what is sometimes called a directed acyclic graph. That structure constrains how programmers can organize their code, and understanding it is the first step to understanding where inheritance works well and where it does not.

  • Single inheritance is the most straightforward pattern: a subclass acquires the features of exactly one superclass. Multiple inheritance extends this by allowing one class to draw from more than one parent, inheriting features from all of them simultaneously. Languages like C++ support multiple inheritance directly.

    Multilevel inheritance chains classes further: a subclass of a subclass. In C++, a class B derived from class A can itself serve as the base class for a class C. The sequence A-B-C is called an inheritance path, and this process can be extended to any number of levels. Class B in that chain is called an intermediate base class because it links inheritance between A and C.

    Hierarchical inheritance runs in the opposite direction: a single base class serves as the parent for more than one subclass. A parent class A might have two separate subclasses B and C, each inheriting from A independently. Hybrid inheritance arises when these patterns are combined. A class A with a subclass B that itself has two subclasses C and D mixes multilevel and hierarchical inheritance in a single structure.

    Eiffel, a language not always in the spotlight, supports a concept called repeated inheritance. That allows a class to inherit from the same superclass more than once, which addresses an edge case the more common models cannot handle: for example, a student who holds two jobs, or attends two institutions simultaneously.

  • In C++, the general form of a derived class uses a colon to signal the inheritance relationship, followed by a visibility modifier that can be either private or public. If no modifier is present, the default is private. Java and C# dispense with this visibility modifier on inheritance; the equivalent of C++'s public derivation is simply the default.

    Visibility matters because it controls which aspects of a base class a derived class can use and expose. Under public derivation, public members of the base class remain public in the derived class. Under private derivation, they become private and inaccessible outside the class chain. Protected derivation sits in between. In C++, Eiffel takes a further step: contracts that define the specification of a class are also inherited by subclasses, binding heirs to the same behavioral promises.

    Overriding is where inheritance becomes dynamic. When a subclass replaces a method it inherited with its own version, the question becomes which version gets called when an object of the inherited type is used. In C++, a method must be explicitly marked virtual to allow dynamic dispatch, which determines at runtime which version to invoke. Java makes all methods virtual by default. Static dispatch, used for non-virtual methods, resolves the call at compile time and is faster; it also enables optimizations such as inline expansion.

    Some languages provide mechanisms to lock down this process. Java's final keyword and C#'s sealed keyword prevent a class from being subclassed at all. C++11 introduced final for the same purpose. These non-subclassable classes allow the compiler to use early binding, because the exact type of any reference is known before the program runs.

  • Implementation inheritance is the mechanism by which a subclass re-uses code from a base class. By default the subclass retains all operations of the base class, but it may override some or all of them. A Java example in the source illustrates this: an abstract SumComputer class defines a transform method that subclasses must implement. The SquareSumComputer subclass implements transform by squaring a number. The CubeSumComputer implements it by cubing. Both subclasses inherit the compute and inputs methods entirely, and only specialize the transformation step.

    But in most programming circles, using class inheritance purely for code reuse has fallen out of favor. The central objection is that implementation inheritance does not guarantee polymorphic substitutability. A reusing class cannot necessarily stand in for an instance of the class it inherited from. Explicit delegation is the main alternative; it requires more code but avoids that substitutability problem. In C++, private inheritance can express an "is implemented in terms of" relationship without implying the is-a relationship that public inheritance carries.

    The distinction between code reuse and subtyping is subtle but important. Subtyping establishes an is-a relationship, while inheritance in the narrow sense only reuses implementation and establishes a syntactic connection. A derived class whose object behaves incorrectly when used in a context where the parent class is expected violates what is known as the Liskov substitution principle. The Go programming language represents a modern approach to this problem: it decouples inheritance from subtyping by design, a lineage that reaches back to language designs from as early as 1990.

  • Allen Holub identified what he called the fragile base class problem as the central flaw of implementation inheritance: modifications to a base class can cause inadvertent behavioral changes in subclasses. Because the subclass shares the implementation of its parent, a change in that parent propagates through the hierarchy in ways that can be hard to predict. Holub's summary was blunt: inheritance breaks encapsulation.

    The authors of Design Patterns voiced similar concerns, advocating instead for interface inheritance and favoring composition over inheritance. The decorator pattern, which the Design Patterns book helped popularize, directly addresses the static nature of inheritance by allowing behaviors to be added at runtime through wrapping rather than through class hierarchy. Role-oriented programming went further, introducing a distinct played-by relationship that combines properties of both inheritance and composition.

    James Gosling, the inventor of Java, reportedly said he would not include implementation inheritance if he were to redesign the language. That is a striking statement about a feature central to Java's design.

    Inheritance also imposes structural constraints that become visible as systems grow. Single inheritance means an object can belong to only one class lineage. The inheritance hierarchy is fixed at the moment an object is instantiated and cannot change at runtime. And whenever client code has access to an object, it generally has access to all of its superclass data, even data the base class author may have preferred to keep contained. The yo-yo problem captures another failure mode: when inheritance was used as the primary structuring tool in the late 1990s, developers sometimes broke code into so many thin layers that debugging became difficult. Some layers contained only one or two lines of actual code.

  • The composite reuse principle offers a direct alternative to inheritance as a structuring strategy. Rather than building class hierarchies, it separates behaviors from the primary class and includes specific behavior classes as needed in any given domain class. This approach allows behavior to be modified at runtime, which the static nature of class hierarchies prevents.

    The Entity-component-system pattern goes further still, allowing program users to define new variations of an entity at runtime rather than in code. This matters because one limitation of inheritance is that subclasses must be defined at compile time; end users cannot add new subclasses while a program is running.

    Despite the sustained criticism, inheritance remains a core feature of C++, Java, Python, C#, Scala, and most widely used object-oriented languages. The debate among programmers and theoreticians dates back to at least the 1990s, and it has not resolved so much as settled into a working consensus: composition is often the better default, but inheritance remains useful for establishing shared interfaces and guaranteeing that classes maintain a common set of methods. The Eiffel language's support for repeated inheritance, allowing a class to inherit from the same superclass more than once, represents one branch of that ongoing exploration of what inheritance can mean when the constraints are loosened.

Common questions

What is inheritance in object-oriented programming?

Inheritance is the mechanism of basing a class upon another class, retaining similar implementation. A child class acquires all the properties and behaviors of its parent class, with the exception of constructors, destructors, overloaded operators, and friend functions of the base class. The relationships formed through inheritance produce a directed acyclic graph.

Who invented inheritance in object-oriented programming?

Ole-Johan Dahl and Kristen Nygaard presented the foundational design in 1967, influenced by Tony Hoare's 1966 remarks on record subclasses. Their idea was first adopted in the Simula 67 programming language and later spread to Smalltalk, C++, Java, Python, and many other languages.

What are the different types of inheritance in OOP?

The main types are single inheritance, where a subclass inherits from one superclass; multiple inheritance, where a class inherits from more than one parent; multilevel inheritance, where a subclass is itself derived from another subclass; hierarchical inheritance, where one base class has more than one subclass; and hybrid inheritance, which combines two or more of these patterns.

What is the fragile base class problem in inheritance?

The fragile base class problem, identified by Allen Holub, is that modifications to a base class can cause inadvertent behavioral changes in subclasses. Because subclasses share the implementation of their parent, changes in the parent propagate through the hierarchy unpredictably. Holub summarized this as inheritance breaking encapsulation.

What is the difference between inheritance and subtyping?

Subtyping establishes an is-a relationship, meaning a subtype can be substituted for another type or abstraction. Inheritance reuses implementation and establishes a syntactic relationship, not necessarily a semantic one. A derived class whose object behaves incorrectly when used in a context where the parent class is expected violates the Liskov substitution principle.

What are the alternatives to inheritance in object-oriented design?

The composite reuse principle separates behaviors from the primary class hierarchy and includes specific behavior classes as needed, allowing runtime modification. Explicit delegation requires more code but avoids the substitutability issues of implementation inheritance. The decorator pattern addresses the static nature of inheritance, and role-oriented programming introduces a distinct played-by relationship combining properties of both inheritance and composition.

All sources

21 references cited across the entry

  1. 1Designing Reusable ClassesRalph Johnson — August 26, 1991
  2. 2BookConference proceedings on Object-oriented programming systems, languages and applications - OOPSLA '89OL Madsen — 1989
  3. 3BookAdvanced Methods and Deep Learning in Computer VisionDavies, Turk — Elsevier Science — 2021
  4. 4Inheritance is not subtypingWilliam R. Cook et al. — 1990
  5. 5Typeful ProgrammingLuca Cardelli — 1993
  6. 6A study of the fragile base class problemLeonid Mikhajlov et al. — Springer — 1998
  7. 7What programmers do with inheritance in JavaEwan Tempero et al. — Springer — 2013
  8. 8Record HandlingC. A. R. Hoare — 1966
  9. 9Class and subclass declarationsOle-Johan Dahl et al. — Norwegian Computing Center — May 1967
  10. 10BookFrom Object-Orientation to Formal MethodsOle-Johan Dahl — 2004
  11. 12BookThe Design and Evolution of C++Bjarne Stroustrup — Pearson — 1994
  12. 13BookThe complete reference C++Herbert Schildt — Tata McGraw Hill — 2003
  13. 14BookObject Oriented Programming With C++E. Balagurusamy — Tata McGraw Hill — 2010
  14. 17BookMastering C++K.R. Venugopal et al. — Tata McGraw Hill Education Private Limited — 2013
  15. 18BookConcepts in programming languageJohn Mitchell — Cambridge University Press — 2002
  16. 19JournalEvolution of object behavior using context relationsLinda M. Seiter et al. — 1996
  17. 20Why extends is evilAllen Holub — 1 August 2003