What Is Inheritance in OOP?
Why write the same thing twice when one class can simply borrow it from another? Inheritance is the idea that lets related pieces of code share what they have in common — while each one still gets to keep what makes it special.
The Big Idea, in One Breath
A way for one class to automatically pick up the properties and abilities of another, so that shared behaviour only ever needs to be written once — and each new class can add whatever makes it unique on top of that shared foundation.
Imagine you are teaching three cousins how to ride a bicycle, a tricycle, and a scooter. All three of them already know how to balance, how to push off with their feet, and how to stop safely — because they all learned to walk and run the same way, years earlier. Nobody has to re-teach “balance” three separate times from scratch. Each cousin simply builds their new, more specific skill on top of something they already share.
That is the whole idea behind inheritance in object-oriented programming. It is a way for one class to automatically pick up the properties and abilities of another class, so that shared behaviour only ever needs to be written once — and each new class can then add whatever makes it unique on top of that shared foundation.
Think about a family recipe passed down through generations. A grandmother’s basic bread recipe gets handed to her children, who each keep the core technique — kneading, rising, baking — but one adds rosemary, another adds cheese, another keeps it plain. Nobody had to reinvent bread-making from zero. They inherited the basics and personalised the rest.
This same pattern shows up everywhere once you start looking for it. A new employee at a company inherits the general dress code, working hours, and email system that everyone else follows, then layers their own specific role on top of that shared foundation. A new smartphone model inherits the basic operating system, camera software, and app store from earlier models, then adds whatever new feature makes that particular model special. Inheritance in programming is really just this same, familiar pattern of “start with what already works, then specialise” — formalised into a rule that computers can follow precisely.
What Inheritance Really Is
A mechanism where a child class acquires the properties and behaviours of a parent class. If you can honestly say “X is-a Y,” inheritance usually fits.
In more formal terms, inheritance is a mechanism where a new class, called a child class or subclass, acquires the properties and behaviours of an existing class, called a parent class or superclass. The child class does not need to redeclare what it already gets automatically — it simply adds new properties, new behaviours, or adjusts the ones it inherited to better fit its own specific needs.
This relationship is often described using the phrase “is-a.” A car is-a vehicle. A savings account is-a bank account. A poodle is-a dog. Whenever that “is-a” sentence sounds natural and true, inheritance is usually a sensible way to model the relationship in code.
“What does it mean for one class to inherit from another?” is really asking: the new class gets a free head start, automatically starting with everything the older class already knows how to do.
It is worth pointing out a small but important distinction here: inheritance describes a relationship between classes — the blueprints — not between the individual objects created from them. When we say Dog inherits from Animal, we mean every single dog object ever created from that blueprint will automatically come equipped with everything an animal is supposed to have, without anyone needing to add it by hand each time. The inheritance is decided once, at the blueprint level, and every object built from that blueprint benefits automatically, forever.
Why It Matters
Small toy programs can live without it. Anything with many related types of things starts to hurt without it fast.
You could, in theory, write every class completely from scratch, repeating shared logic in each one. Plenty of very small programs do exactly that, and it is fine at a tiny scale. But as soon as a program grows to include many related types of things, skipping inheritance starts to hurt. Here is what inheritance actually buys a team:
Write it once, use it everywhere
Shared behaviour lives in one place, so it never needs to be typed out again for every related class.
Related things behave predictably
Every child class automatically follows the same foundational rules as its parent, keeping the system predictable.
Fix it once, fix it everywhere
A bug found in shared, inherited behaviour only needs to be corrected in the parent class, and every child benefits instantly.
Grow the family easily
Adding a new, related type of object is usually as simple as creating one more child class, without disturbing the existing ones.
There is also a very human benefit worth mentioning: inheritance helps a codebase read a little more like the real world it is modelling. When a new engineer sees that SavingsAccount and CheckingAccount both extend BankAccount, they immediately understand something true about how the system is organised, without needing to read a single line of the actual logic yet.
Consider the alternative for a moment, just to appreciate what inheritance saves a team from. Without it, a banking system with five different account types would need the same interest calculation, the same balance-checking logic, and the same transaction history tracking copied into five separate places. If the bank later discovers a bug in how interest gets calculated, someone has to remember to fix it in all five locations — and if even one gets missed, that account type quietly keeps producing incorrect results while the others have already been corrected. Inheritance turns that fragile, error-prone situation into a single fix in one place, automatically inherited by every account type that depends on it.
How It Actually Works
One line declaring the relationship. Everything shared flows down automatically. Only the truly distinctive behaviour needs to be written on top.
Let us look at a simple, concrete example. Imagine a small system for a pet shelter that needs to track different kinds of animals.
class Animal: def __init__(self, name, age): self.name = name self.age = age def eat(self): return f"{self.name} is eating." def sleep(self): return f"{self.name} is sleeping." class Dog(Animal): def bark(self): return f"{self.name} says woof!" buddy = Dog("Buddy", 3) print(buddy.eat()) # Buddy is eating. (inherited) print(buddy.bark()) # Buddy says woof! (its own)
Notice that Dog never had to define eat() or sleep() itself — those came along automatically the moment it declared itself as extending Animal. All it had to add was the one thing that makes a dog different from a generic animal: barking. This is the entire mechanical heart of inheritance — one line declaring the relationship, and everything shared flows down automatically.
It is worth noticing something else in that example too: the shelter system could now easily add a Cat class, a Rabbit class, or a Parrot class, and every single one of them would automatically know how to eat and sleep without a single extra line of code for those two actions. Only the truly distinctive behaviour — barking, meowing, hopping, chirping — needs to be written separately for each one. This is exactly the kind of efficient, organised growth inheritance is designed to support.
Types of Inheritance
Depending on how many parents and children are involved, and how they connect, inheritance takes on five recognisably different shapes.
Inheritance does not only come in one shape. Depending on how many parents and children are involved, and how they connect to each other, it takes on a few recognisably different patterns. Understanding these shapes helps enormously when reading someone else’s code for the first time, because recognising the pattern immediately tells you something about how the classes relate to each other before you have read a single method.
Single Inheritance
One child class inherits from exactly one parent class. This is the simplest and most common form, and it is what the Dog and Animal example above demonstrates. It is also the easiest to reason about, since there is never any ambiguity about where a particular inherited behaviour originally came from — there is only ever one place to look.
Multilevel Inheritance
A child class becomes the parent of another class, forming a chain — like grandparent, parent, and child in a family tree. Each level adds its own new abilities on top of everything inherited from above. In the diagram below, a SportsCar is-a Car, and a Car is-a Vehicle, which means a SportsCar automatically inherits everything from both Car and Vehicle, all the way up the chain, even though it only directly extends Car.
Hierarchical Inheritance
Multiple child classes all inherit from the same single parent class. This is the shape used in the Bird and Fish example seen in other guides on this topic, and it is extremely common wherever a family of related types shares one foundation. It is especially useful when a team already knows, from the start, that several different but related types of objects will need to exist side by side, all sharing some common core.
Multiple Inheritance
A single child class inherits from more than one parent class at the same time, combining features from each. Some languages, like Python, allow this directly. Others, like Java, do not allow it for classes, largely because it can create confusing situations if two parents happen to define something with the same name. In the diagram below, a Duck can both fly and swim, so it makes conceptual sense for it to inherit abilities from both a Flyer type and a Swimmer type at once — no single-parent hierarchy captures that dual nature quite as naturally.
Hybrid Inheritance
A combination of two or more of the patterns above, used together within the same design — for example, a hierarchy that also includes a multilevel chain somewhere within it. Hybrid inheritance is not a separate mechanism so much as a description of what happens naturally in a large, real-world system where several of these shapes show up side by side.
A large e-commerce platform is a good example of how hybrid inheritance tends to appear in practice. A general Product class might sit at the top, with Electronics and Clothing branching off it hierarchically. Electronics might then branch further into Smartphones and Laptops, adding a multilevel chain underneath one branch of the hierarchy. None of this was planned as “hybrid inheritance” on purpose — it simply emerged naturally as the catalog grew more detailed over time, which is exactly how most hybrid structures come to exist in real systems.
Overriding: Making Inherited Behaviour Your Own
Inheritance does not mean stuck with the parent’s behaviour exactly as-is. Most languages let a child class replace it with its own version that fits better.
Inheritance does not mean a child class is stuck using its parent’s behaviour exactly as-is. Most object-oriented languages allow a child class to override an inherited action, replacing it with its own version that better fits its specific needs.
class Animal: def make_sound(self): return "Some generic animal sound" class Cat(Animal): def make_sound(self): return "Meow!" class Cow(Animal): def make_sound(self): return "Moo!"
Both Cat and Cow inherit make_sound() from Animal, but each one replaces it with something more accurate for its own kind. This is closely connected to the idea of polymorphism — the same instruction, make_sound(), produces a different, fitting result depending on which specific object receives it.
Sometimes, a child class wants to build on top of the parent’s version rather than fully replace it. Many languages provide a way to explicitly call the parent’s original version from inside the overriding method — often using a keyword like super — so the child can add a little extra behaviour without having to retype everything the parent already handled.
Overriding lets a family of related classes share a common structure while still allowing each member to behave correctly for its own specific situation — the same way every animal breathes, but not every animal makes the same sound.
It helps to distinguish overriding from something that sounds similar but is actually quite different: simply adding a brand-new method that the parent never had at all. Overriding specifically means replacing something the parent already defined, keeping the same name so that anyone calling that action gets the child’s more specific version automatically, without needing to know or care which exact class they are dealing with. Adding an entirely new method, like Dog‘s bark() in the earlier example, is not overriding — it is simply extending, since Animal never had a bark() method to begin with.
What Gets Passed Down, and What Does Not
Access modifiers — public, protected, private — decide how openly each piece of information or behaviour is shared with the outside world and with child classes.
Not everything inside a parent class automatically becomes fully available to a child class in exactly the same way. Most object-oriented languages offer a way to control visibility, often called access modifiers, which decide how openly a piece of information or behaviour can be shared.
Think about a family house. Some rooms, like the living room, are open to anyone who visits — that is public. Some rooms, like a home office, are only open to family members — that is protected, shared within the family but not with outside guests. And some things, like a personal diary locked in a drawer, are not meant to be accessed by anyone else at all, not even close family — that is private.
| Visibility | Who Can Access It | House Analogy |
|---|---|---|
| Public | Any code anywhere, including outside the class family entirely | The living room — open to any visitor |
| Protected | The class itself and any of its child classes | The home office — open to family only |
| Private | Only the exact class that defined it, not even child classes | The locked diary — nobody else gets in |
This matters directly for inheritance because it determines exactly how much of a parent class a child class is actually allowed to touch. A child class can freely use anything public or protected from its parent, but anything marked private in the parent stays fully hidden, even from its own children. This might sound restrictive at first, but it is actually a deliberate safety feature: it lets a parent class keep its most sensitive, easily-broken internal details fully protected, while still sharing everything that is genuinely safe and useful to pass down.
Without this kind of control, every child class could reach into every last detail of its parent, including fragile internal bits never meant to be touched directly. Access modifiers let a parent class say, in effect, “here is what you can build on, and here is what you should leave alone.”
Inheritance vs. Composition
“Is-a” versus “has-a.” A car is-a vehicle, but it has-a engine. Both are ways to reuse code — only one is a good fit at a time.
Inheritance is powerful, but it is not the only tool for reusing code, and it is not always the right one. A closely related alternative, called composition, builds new objects by combining smaller objects together, rather than extending a shared parent.
| Question | Inheritance | Composition |
|---|---|---|
| Core relationship | “is-a” — a Car is-a Vehicle | “has-a” — a Car has-a Engine |
| Flexibility | Fixed at design time, harder to change later | Easier to swap parts at runtime |
| Coupling | Child is tightly bound to parent’s design | Looser — objects interact through simple interfaces |
| Best suited for | Clear, stable hierarchies of related types | Flexible combinations of independent behaviours |
Using our earlier example: a Car genuinely is-a Vehicle, so inheritance fits naturally. But a Car has-a Engine — a car is not a type of engine, it simply contains one. Modelling that second relationship with inheritance would be a mismatch; composition is the better fit there.
Many experienced developers follow a guideline often summarised as “favour composition over inheritance” — not because inheritance is bad, but because composition tends to create more flexible, less tightly tangled designs, especially as a system grows larger over time.
A good way to picture the practical difference is to imagine needing to change an engine in a real car versus needing to change what a car fundamentally is. Swapping a car’s engine for a newer, more efficient model does not require rebuilding the entire car — because the engine is a separate, composed part that can simply be swapped out. But if a car’s entire “is-a Vehicle” relationship needed to change — say, redefining the whole Vehicle hierarchy — every single class extending it would be affected at once, which is a much bigger, riskier change. Composition tends to keep these two concerns cleanly separated, which is exactly why many developers reach for it more often than they used to.
Weighing It Fairly: Pros and Cons
Not universally good or bad. Its value depends entirely on whether it is being used to model a relationship that is actually true.
Like most tools in software design, inheritance is not universally good or bad — its value depends entirely on whether it is being used to model a relationship that is actually true. A fair evaluation means looking honestly at both what it gives a team and what it can quietly cost if applied carelessly.
Strengths
- Avoids repeating the same code in multiple related classes.
- Makes relationships between types explicit and easy to understand.
- A single fix in the parent class benefits every child automatically.
- New related classes can be added quickly by extending an existing one.
Trade-offs
- Creates tight coupling — child classes depend heavily on their parent’s design.
- Changes to a parent class can unexpectedly affect every child class.
- Deep inheritance chains become genuinely hard to trace and debug.
- Not every relationship is truly an “is-a” relationship, even when it is tempting to force one.
A fair way to summarise this: inheritance is an excellent tool when the relationship between classes is genuinely stable and hierarchical, but it can quietly become a liability when it is used simply as a shortcut to avoid retyping a few lines of code.
One more subtlety worth mentioning: the cost of tight coupling is not always obvious on day one. A parent-child relationship that feels perfectly clean at the start of a project can become a genuine burden two years later, once the parent class has grown to support many more children than originally imagined, each with slightly different needs pulling the shared design in different directions. This is precisely why experienced developers tend to think carefully before committing to an inheritance relationship, rather than adopting it automatically the first time two classes happen to share a little code.
Common Pitfalls to Avoid
Four well-known traps — the diamond problem, overuse for simple reuse, chains that go too deep, and quietly breaking parent assumptions.
Even developers who understand inheritance well can fall into a handful of well-known traps. Recognising them ahead of time makes them far easier to avoid.
The Diamond Problem
This shows up specifically with multiple inheritance, when a class inherits from two parents that both, in turn, inherited from the same common ancestor. If both parents override a shared method differently, the child class faces genuine ambiguity: which version should it actually use? Different languages solve this puzzle in different ways — some forbid multiple inheritance for classes entirely, while others define strict, predictable rules for which version wins.
Overusing inheritance for simple code reuse
Reaching for inheritance purely to avoid retyping a small piece of logic, even when there is no true “is-a” relationship, tends to create awkward, confusing hierarchies that are hard to justify later. Composition is often the more honest tool for that specific goal. A classic warning sign is when a child class ends up needing to override or disable several of its parent’s methods just to behave correctly — that is usually a strong signal that the “is-a” relationship was never actually true in the first place.
Building inheritance chains that are too deep
A chain of five or six generations of classes, each extending the one before it, becomes genuinely difficult for anyone to trace. Understanding a single deeply nested child class might require reading through every ancestor above it, which slows everyone down. It also makes onboarding new team members noticeably harder, since they cannot safely modify a low-level class without first tracing its entire ancestry to understand every behaviour that might ripple down.
Breaking behaviour parent code relies on
Occasionally, a parent class is written with certain assumptions about how its methods will be used together — assuming, for instance, that one method is always called before another. When a child class overrides one of those methods without preserving that underlying assumption, it can quietly break behaviour the parent class depends on, in ways that are surprisingly difficult to trace back to the actual cause.
Changing a parent class without checking how many child classes depend on it. A seemingly small tweak to shared behaviour can ripple outward and quietly break several unrelated-looking parts of a system at once.
Where You Will Meet This Every Day
Once you know the pattern, it becomes almost impossible not to notice it — from messaging apps and games to browsers and biological classification.
Inheritance quietly shapes a huge amount of the software running behind everyday apps and devices. Once you know the pattern, it becomes almost impossible not to notice it everywhere.
| Everyday Example | How Inheritance Shows Up |
|---|---|
| A messaging app | Text messages, photo messages, and voice messages all share common features — a sender, a timestamp — inherited from a general “Message” type. |
| A game with characters | Knights, wizards, and archers all inherit shared traits like health points from a common “Character” type, while adding their own special abilities. |
| A user interface library | Buttons, checkboxes, and sliders often inherit shared behaviour like being clickable or drawable from a common “Control” type. |
| A shopping app | Electronics, clothing, and groceries might all inherit shared properties like price and quantity from a general “Product” type. |
Common inheritance patterns
Single, multilevel, hierarchical, multiple, and hybrid — enough shapes to describe just about every real-world hierarchy.
Shared foundation, many variations
One parent class defines the common ground; each child adds its own distinct flavour on top.
Of proven use across real systems
The pattern has quietly powered production software since object-oriented programming first entered mainstream use.
Biology and file systems
Species, genus, and family; folders and their subfolders — the same hierarchical instinct at work far outside software too.
It is worth noting that inheritance does not only appear inside application code written by developers — it also appears throughout the very tools and libraries developers rely on every day. Web browsers organise different kinds of visual elements — buttons, images, paragraphs — using shared inherited behaviour. Operating systems organise different kinds of files and windows the same way. Even outside of programming entirely, the biological classification of living things, sorting animals into species, genus, and family, follows a strikingly similar hierarchical structure, which is part of why the concept tends to feel intuitive once it is explained with a familiar example.
This pattern also extends into how large software companies structure their own internal frameworks. A team building a suite of related tools — say, several different kinds of internal dashboards — will often design one shared base dashboard class carrying the common layout, navigation, and theming, letting each specific dashboard team focus only on what makes their particular view unique. The result is dozens of dashboards that all feel consistent to the people using them, built by teams who never had to individually solve the same layout and navigation problems from scratch.
Questions People Often Ask
Seven honest questions that come up over and over — and honest answers to each.
Can a child class have more than one parent?
It depends on the language. Some languages, like Python, allow a class to inherit from multiple parents directly. Others, like Java, only allow a class to extend one parent class at a time, though they usually offer a separate mechanism, often called an interface, to achieve some of the same benefits more safely.
Does a child class have to use everything it inherits?
No. A child class automatically has access to everything its parent offers, but it is under no obligation to actively use every single inherited piece. It simply has the option available whenever it is needed.
Is inheritance the same thing as copying code?
Not at all, and this is a common point of confusion. Copying code creates two separate, disconnected versions of the same logic — if a bug is fixed in one, the other stays broken. Inheritance keeps a single, shared source of truth in the parent class, so a fix made there automatically applies to every child class that relies on it.
How deep should an inheritance chain go?
There is no universal number, but most experienced developers try to keep chains shallow — often no more than two or three levels — specifically because deeper chains become significantly harder to read, trace, and safely change over time.
Is inheritance still considered a good practice today?
Yes, when used thoughtfully. It remains a genuinely valuable tool for modelling clear, stable “is-a” relationships. What has changed over the years is a growing awareness that composition often deserves more attention than it traditionally received, and that inheritance should not be treated as the automatic first choice for every kind of code reuse.
What happens if a child class does not override anything at all?
Nothing unusual — it simply behaves exactly like its parent for every inherited action, while still counting as its own distinct type. This is actually a perfectly normal and common situation, especially early in a project, before a specific child class has developed any behaviour that genuinely needs to differ from its parent.
Can a parent class prevent a child from overriding a certain method?
In many languages, yes. Developers can mark a specific method as “final” or otherwise protected from being overridden, which is useful when a piece of behaviour is considered so fundamental or so easy to get wrong that no child class should be allowed to replace it, no matter how tempting that might be.
Key Takeaways
A way of letting related things share what they have in common without forcing anyone to type it out more than once — a familiar pattern long before it ever had a technical name.
Inheritance, at its heart, is simply a way of letting related things share what they have in common, without forcing anyone to type it out more than once. From a family tree, to a recipe passed down through generations, to a bicycle-riding cousin who already knows how to balance, the underlying pattern was familiar long before it ever had a technical name. The next time you open an unfamiliar codebase and spot a class quietly extending another, it is worth pausing for a moment to trace what it actually inherited — more often than not, that one relationship explains a surprising amount about how the whole system was designed to think.
Remember this
- Inheritance lets a child class automatically acquire the properties and behaviours of a parent class, expressed through an “is-a” relationship.
- It comes in several recognisable patterns — single, multilevel, hierarchical, multiple, and hybrid — depending on how classes connect.
- Overriding lets a child class replace or extend inherited behaviour to better fit its own specific needs.
- Composition, a “has-a” relationship, is often a more flexible alternative when the classes do not truly share a stable hierarchy.
- Used well, inheritance reduces repetition and keeps related code consistent; used carelessly, it can create tight coupling and confusing, deep chains.
- The healthiest habit is asking whether the “is-a” relationship is genuinely true before reaching for inheritance out of convenience.