How do you effectively model inheritance in a database?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Relational databases do not have object-oriented inheritance built in, so every inheritance design is a mapping choice rather than a native feature. The effective model depends on what you optimize for: query simplicity, normalization, update discipline, or compatibility with your ORM.
The Three Common Strategies
Most database inheritance discussions reduce to three patterns:
- single-table inheritance
- class-table inheritance
- concrete-table inheritance
Each is valid. The mistake is acting as if one pattern is universally best.
Single-Table Inheritance
In single-table inheritance, every subtype lives in one table and a discriminator column tells you which kind of row it is.
A car row might use doors, while a truck row might use payload_kg. Columns that do not apply to a subtype are left NULL.
This pattern is attractive when:
- the hierarchy is small
- reads are frequent
- you want simple queries without joins
Its biggest drawback is sparsity. As the hierarchy grows, the table accumulates many subtype-specific columns and lots of NULL values.
Class-Table Inheritance
Class-table inheritance keeps shared fields in a base table and subtype fields in separate child tables.
This is more normalized and keeps subtype-specific fields where they belong. It is a strong fit when the hierarchy is stable and data integrity matters more than having one-table reads.
The tradeoff is query complexity. Fetching a full subtype often requires joins, and polymorphic queries across the hierarchy can become heavier.
Concrete-Table Inheritance
Concrete-table inheritance gives each subtype its own full table, including columns duplicated from the conceptual base type.
This avoids joins and avoids sparse columns, but it duplicates shared fields such as make. That duplication increases maintenance cost when shared attributes evolve.
This pattern is best when subtypes are truly separate in most queries and you rarely need polymorphic operations across the whole hierarchy.
How to Choose Effectively
A useful selection rule is:
- choose single-table inheritance for small hierarchies and fast simple reads
- choose class-table inheritance when normalization and subtype integrity matter most
- choose concrete-table inheritance when subtype tables are operationally separate and polymorphic queries are rare
Also ask how the application reads the data.
If most requests say "load this thing regardless of subtype," one-table reads may be attractive.
If most requests say "load trucks as trucks and cars as cars," separate subtype tables may be cleaner.
ORM Considerations
ORMs often expose these same patterns directly. For example, JPA and Hibernate support variants of all three strategies.
That support is helpful, but the database design should still be driven by data access patterns, not only by which annotation looks easiest today. ORMs can make a poor schema easier to write, but they do not remove the underlying tradeoffs.
A common failure mode is choosing inheritance because the object model has inheritance, even when the database would be simpler with composition or separate related tables.
When Not to Model Inheritance at All
Sometimes the best answer is not inheritance. If the shared fields are minimal and the behaviors are operationally different, forcing a hierarchy can create more complexity than it saves.
Likewise, if subtype-specific attributes are highly dynamic, a different modeling strategy such as composition, related detail tables, or carefully designed JSON columns may fit better than a rigid inheritance map.
Common Pitfalls
Choosing single-table inheritance for a large, fast-growing hierarchy often leads to bloated tables full of mostly irrelevant columns.
Choosing class-table inheritance without considering query cost can create join-heavy read paths that are harder to tune.
Choosing concrete-table inheritance and then needing frequent cross-type reporting leads to repetitive union queries and duplicated logic.
The biggest conceptual mistake is assuming the database must mirror the object model exactly. Relational design and object design serve related but different goals.
Summary
- database inheritance is a modeling choice, not a built-in relational feature
- single-table inheritance favors simple reads but creates sparse schemas
- class-table inheritance is normalized but requires more joins
- concrete-table inheritance avoids joins at the subtype level but duplicates shared columns
- choose the pattern based on query behavior, integrity needs, and operational simplicity rather than on ORM defaults alone
Related reading
- How do you get the index of the current iteration of a foreach loop?
- How do you include postgresql.conf on docker container when using org.testcontainers
- How do you like your primary keys?
- How do you manage databases in development, test, and production?
- How do you find all subclasses of a given class in Java?
- How do you implement the Singleton design pattern?
- How do you perform Django database migrations when using Docker-Compose?
- How do you query for a non-existent null attribute in DynamoDB

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.