Why Modern Developers Avoid Inheritance

Analyzes the shift from classical object-oriented inheritance toward composition-oriented design in modern development, especially in frontend ecosystems.

This note continues from API Exception Mapping and Validation Strategy , Inheritance and Composition in Business Application Design , and Form AJAX Standardization .

Introduction

I was trained in a more classical object-oriented style.

In my early professional years, inheritance was used very naturally. The distinction between is-a and has-a relationships was treated as one of the basic foundations of design, and that way of thinking still remains part of how I structure systems today.

That does not mean I try to force inheritance everywhere. I use composition where it makes more sense, and in many parts of modern application design it clearly does. But I have never felt that inheritance itself should be rejected automatically.

Limited Use of Inheritance in Models

Even so, I am careful about inheritance in application models.

In domain models, inheritance can become expensive very quickly. Once many concrete types depend on a shared base, a small modification in that base can propagate through a large area. The verification cost rises with it, and the risk is often not visible until the model has already spread through many screens and services.

Discovering the Cultural Shift

What surprised me over time was not the recommendation to use composition in some areas. What surprised me was how often modern discussions treated inheritance itself as something undesirable.

When I read discussions online, I often found a stronger position than I expected. Some developers did not just say that composition is often better. They said inheritance should never be used at all.

What felt particularly strange to me was that inheritance and composition were sometimes discussed as though they were parallel options from which one should be selected for an entire system. From my perspective, they are not that kind of pair. They describe different relationships and answer different design needs.

An inheritance relationship and a composition relationship are not interchangeable in the first place. The question should usually be which requirement is being modeled, and which structural relationship fits that requirement. It does not make much sense to decide at the whole-project level that one of them should replace the other everywhere.

My own experience comes primarily from Japanese development environments, so I do not claim this article as a universal conclusion. I have worked in Japan, and much of the information I gather in day-to-day practice is also filtered through Japanese-language discussions and Japanese development culture. So it is entirely possible that the global picture is somewhat different from what I have observed. This article is closer to a personal observation about how design culture appears to have shifted when viewed from that background.

Framework Influence

One obvious factor is the influence of modern frontend frameworks.

React and similar frameworks strongly normalized composition-oriented thinking. Inheritance is still technically possible in such ecosystems, but it is rarely the recommended way to organize application behavior. The surrounding patterns, examples, and community habits all guide developers toward composition instead.

This is not accidental. Those frameworks were designed so that developers could structure complex UI behavior without depending on inheritance hierarchies. Once a generation of developers learns architecture through that style, the cultural default naturally changes with it.

Personal Position

My own position is still fairly simple. Inheritance should not be feared. It remains useful in some contexts, and Cotomy itself uses inheritance in its foundation classes.

I have also noticed that many developers of roughly my own generation do not instinctively hate inheritance either, even when they are quite cautious about where to apply it. So I do not think this difference is only about theory. It also seems to reflect when and where a person learned to build software.

At the same time, I understand why user-level design in many frameworks prefers composition. There is no contradiction there. A framework can rely on inheritance inside its own structural layers while still giving application developers a way to organize the parts of what they want to build as a set of assembled pieces, and to realize varied behavior without depending on inheritance at the application level.

Risks of Inheritance

I do agree that inheritance creates real risks when used carelessly.

In domain models, a base class can create widespread coupling. The more shared assumptions accumulate in that base, the harder it becomes to change anything safely.

In UI components, the danger is slightly different. A base class change can alter rendering behavior, event wiring, or layout assumptions across many screens at once. The impact analysis becomes harder because the dependency is structural rather than local.

So I do not disagree with the warning. Careless inheritance should be avoided.

In fact, when the goal is simply future change resistance, separately defining similar properties or behavior can sometimes be safer than forcing them into one inheritance line too early. Shared structure reduces duplication, but it also concentrates impact.

The Real Question

The real issue is not inheritance itself, but how inheritance is used.

That is the question that matters more than whether inheritance is good or bad in the abstract.

Inheritance tends to fail when the base class is not a meaningful abstraction, but only a container for utility methods or a place to collect unrelated shared fields. At that point the inheritance line no longer represents an object-oriented relationship. It becomes only a distribution mechanism for convenience code.

That kind of structure is usually where people start to hate inheritance, and understandably so.

To me, inheritance makes sense when the base type still has meaning as a type, even if it cannot exist directly as a concrete instance. What matters is that the base is not merely a storage place for common code, but a clearly meaningful thing in its own right, with more specific screens or objects derived from it.

In that sense, I sometimes think old textbook examples did not help very much. Explanations built around mammal and human, or employee and clerk, may have been intended to teach classification, but they did not always help people understand inheritance as a design tool. They could make inheritance look like a taxonomy exercise rather than a structural way to express a meaningful abstraction and its more concrete forms.

A useful comparison is the old Windows Forms model. A Form represented a screen and exposed operations appropriate to a screen, while the detailed behavior belonged to each derived screen class. Inside that screen, controls were then assembled through composition. That division always felt reasonable to me. Form had a clear independent meaning as Form, and each individual screen existed as a derived form built on that role. Inheritance defined the structural role of the screen, and composition defined what the screen contained.

A Real Project Example

I remember an older ASP.NET project where many controllers inherited from a common base class. At first glance, that looked ordinary. But the base class had gradually turned into a storage area for unrelated helpers.

User information retrieval lived there. Data formatting lived there. Message creation lived there. Several small operational shortcuts were added there as the project expanded.

public abstract class BaseController : Controller
{
    protected UserContext CurrentUser => ...;
    protected string FormatAmount(decimal value) => ...;
    protected string CreateMessage(string code) => ...;
}

This was not really object-oriented abstraction. It was closer to structured programming implemented through classes. The class hierarchy was carrying procedural convenience functions, not a stable conceptual model.

When developers see inheritance in that form repeatedly, it is not surprising that they become suspicious of inheritance itself.

Why Teams Sometimes Ban Inheritance

Because of that history, banning inheritance can be a rational rule in some environments.

On large projects, teams are often assembled under constraints that have little to do with ideal design. Hiring quality is uneven. External engineers rotate in and out. Skill levels vary more than resumes suggest. In some cases, very large systems are built by temporarily gathering many people who have never worked together before.

I have seen projects in Japan where this was completely normal. A team of more than twenty people could be assembled for a single web system, while only a handful actually belonged to the main vendor and the rest were effectively a temporary collection of outside engineers. I was one of those people myself more than once, so I do not say that from a distance.

In that kind of environment, the difficult part is not only writing code. The difficult part is predicting how safely each developer will extend existing structures. Sometimes the variation is so wide that architectural freedom itself becomes a source of risk.

The unpleasant reality is that resumes and interviews do not tell you enough. People naturally present themselves at their best during hiring, and in practice it was not rare to discover after onboarding that someone could not safely handle the level of design responsibility they had implied. That is not a theoretical staffing problem. On some projects it becomes part of the architecture problem.

Under those conditions, a blanket rule such as avoid inheritance can be a practical control measure. It reduces one category of high-impact mistakes, even if it also removes a legitimate tool.

Seen from that angle, an inheritance ban is not necessarily an intellectual position. It can be an operational safeguard.

Frontend Language Constraints

Another factor may be the historical shape of frontend languages themselves.

Early JavaScript did not make classical object-oriented design especially pleasant. Class definitions were awkward compared with languages that treated classes as a first-class syntax and design boundary. Many developers simply avoided object-oriented structure because the language did not reward it.

I remember very clearly looking at old JavaScript class-style patterns and feeling that trying to preserve a classical OOP style there was more trouble than it was worth. That was one reason I accepted jQuery quite naturally at the time. The language could support object-oriented programming in a broad sense, but it did not feel like a language that wanted to help me do it well.

Libraries such as jQuery also did not require users to think in object-oriented terms. Internally they may have had their own abstractions, but users were not asked to model screens or behavior through inheritance hierarchies. A great deal of frontend work could be done without formal modeling, inheritance design, or explicit class structure. At least to me, that environment seemed to make it easier for frontend practice to develop at some distance from classical OOP habits.

Expansion of Frontend Development

There may also be a broader social reason.

As frontend development expanded rapidly, many developers entered the field from design-oriented or markup-oriented backgrounds. Some of them were excellent implementers, but not necessarily trained through the traditional object-oriented curriculum that earlier business application developers often went through.

My impression is that this may have reinforced composition-first thinking, because frontend work often centered on assembly, interaction, and presentation rather than modeling. Developers who specialized only in frontend work may simply have had fewer chances to become comfortable with object-oriented modeling, especially inheritance-based modeling. That is only a hypothesis, not a proven explanation, but it seems plausible to me.

A Balanced Interpretation

For that reason, I do not think the modern avoidance of inheritance is necessarily wrong.

Software engineering evolves through repeated experimentation. Teams keep the methods that reduce failure in their own context. If avoiding inheritance gives a team more predictable results, that may be the correct choice for that environment.

The goal is not ideological purity. The goal is to build reliable systems.

Cotomy and Inheritance

My own conclusion remains practical. Cotomy uses inheritance in its core architecture, because framework-level abstractions often benefit from it.

CotomyForm extends CotomyElement, and more specialized form classes are built on top of that line. CotomyPageController is also designed as an inherited structural base for page-level behavior. Application-level modeling, however, often benefits from more selective composition. Those are different design layers, so I do not expect them to follow exactly the same rules.

Cotomy does not try to eliminate inheritance entirely. It tries to use it where the abstraction is structural and stable.

For that reason, my expectation is that Cotomy will probably feel more natural to developers who mainly build server-oriented business applications and want a stronger frontend structure for those systems, rather than to developers whose habits were formed entirely inside modern composition-first frontend ecosystems. If that is true, Cotomy users may not be especially inclined to reject inheritance in the first place.

Conclusion

Inheritance is not inherently harmful. What causes trouble is misuse.

Modern development culture may avoid inheritance for understandable reasons: painful historical experience, team structure, framework influence, and language ecosystem constraints. Different parts of the industry can develop very different architectural instincts from those conditions.

I find that difference interesting in itself, because software design culture is shaped not only by theory, but also by tools, teams, and the environments in which people learned to build systems.

Design Series

This article is part of the Cotomy Design Series, which explores architectural decisions behind the framework.

Series articles: CotomyElement Boundary , Page Lifecycle Coordination , Form AJAX Standardization , Inheritance and Composition in Business Application Design , API Exception Mapping and Validation Strategy , Why Modern Developers Avoid Inheritance, and Inheritance, Composition, and Meaningful Types .

Next article: Inheritance, Composition, and Meaningful Types

Learn Cotomy

Cotomy is a DOM-first UI runtime for long-lived business applications.