skip to content

An ORM needs to map a class hierarchy - an Employee base class with Salaried and Hourly subclasses - onto relational tables. Compare Single Table Inheritance, Class Table Inheritance, and Concrete Table Inheritance for doing this, including what happens to nullable columns, joins, and polymorphic queries in each.

level: seniorimportance: must knowfreq 70%

answer

  1. single table = one wide table + discriminator
  2. class table = base + per-subclass tables, join by shared PK
  3. concrete table = one self-contained table per subclass, no shared base
  4. nulls vs joins vs duplication is the trade-off axis
  5. polymorphic query cost differs sharply per strategy

basics

~20 s

Single Table Inheritance puts every subclass's columns in one wide table with lots of nulls. Class Table Inheritance splits shared and subclass-specific columns into separate tables joined by shared ID. Concrete Table Inheritance gives each subclass its own fully self-contained table, duplicating the shared columns.

solid answer

~60 s

Single Table Inheritance maps the whole hierarchy to one table with a discriminator column identifying the subclass; it needs no joins and makes polymorphic queries trivial, but subclass-specific columns are nullable for rows of other subclasses, wasting space and weakening constraints as the hierarchy grows. Class Table Inheritance gives the base class its own table and each subclass a table holding only its extra columns, linked by a shared primary key; it keeps the schema normalized and avoids null columns, but loading a subclass instance requires a join across as many tables as there are levels of inheritance involved, and polymorphic queries across all subclasses require an outer join or union. Concrete Table Inheritance gives each concrete subclass a fully self-contained table including copies of the base class's columns; it avoids joins for single-subclass queries and keeps each table's columns tightly non-null, but duplicates shared columns across tables, complicates schema evolution (a base-class column change means altering every subclass table), and makes polymorphic queries across the hierarchy require a UNION.

go deeper

for a junior

Should recognize that a class hierarchy has to somehow be flattened into tables, and that different strategies exist.

for a middle

Should describe the basic shape of each strategy - one table, base+subclass tables, or fully separate tables.

for a senior

Should articulate the concrete trade-offs (nullable columns, join cost, duplication, constraint enforcement) and pick a strategy given a described hierarchy's depth and query patterns.

for a principal

Should reason about how the choice affects long-term schema evolution, cross-team ownership of subclass tables, and migration cost if the hierarchy needs to change strategy later at scale.

## The mismatch all three solve All three strategies solve the same underlying mismatch: object-oriented inheritance lets a base class and its subclasses share behavior and state through a type hierarchy, but a relational table is a flat, single-shaped structure with no native concept of 'this row is also a more specific kind of row.' Given Employee as a base class with Salaried and Hourly subclasses (Salaried adding an `annualSalary` column, Hourly adding an `hourlyRate` and `hoursPerWeek`), each strategy answers the question 'how many tables, and which columns go where' differently. ## Single Table Inheritance Single Table Inheritance puts the entire hierarchy into one table - `employees` - with a superset of all columns from every subclass (`name`, `hire_date`, `annual_salary`, `hourly_rate`, `hours_per_week`, ...) plus a **discriminator column** (commonly named something like `employee_type`) that records which subclass each row actually represents. - **Loading any employee**, regardless of subclass, is a single `SELECT` with no joins, and a polymorphic query like 'find all employees hired this year' naturally spans every subclass at once because they all live in the same table. - **The cost** is that subclass-specific columns are meaningless, and therefore must be nullable, for every row belonging to a different subclass - a Salaried row has null `hourly_rate` and `hours_per_week`, and an Hourly row has null `annual_salary`. As the hierarchy grows (more subclasses, more subclass-specific fields), the table accumulates more and more sparse, mostly-null columns, weakens the database's ability to enforce `NOT NULL/CHECK` constraints on subclass-specific data (since the constraint must be conditional on the discriminator, which most databases can't express directly), and can approach practical column-count limits. ## Class Table Inheritance Class Table Inheritance instead gives the base class its own table (`employees`, holding `name`, `hire_date`, and the shared primary key) and gives each subclass its own table holding only its additional columns (`salaried_employees` with `annual_salary`, `hourly_employees` with `hourly_rate` and `hours_per_week`), where each subclass table's primary key is also a foreign key back to the base table's primary key - a direct application of the Identity Field pattern shared across the join. - **This keeps the schema normalized**: no column is ever null because it belongs to a different subclass, since a row simply doesn't exist in a subclass table it doesn't belong to. - **The cost is joins**: loading a fully-typed Salaried employee requires joining `employees` to `salaried_employees`, and a polymorphic query across the whole hierarchy (all employees, whichever subclass) requires an outer join to every subclass table, or a strategy of always querying the base table and lazily joining subclass details only when the specific subclass is known - more query complexity and more round trips or larger joins than Single Table Inheritance. ## Concrete Table Inheritance Concrete Table Inheritance instead gives each concrete subclass its own completely self-contained table - `salaried_employees` with `name`, `hire_date`, and `annual_salary` all present, and `hourly_employees` with `name`, `hire_date`, `hourly_rate`, and `hours_per_week` all present - with no shared base table at all. - **Loading a known-subclass instance** needs no join whatsoever, and every column in every table is meaningfully non-null for that subclass. - **The costs are schema duplication** (`name` and `hire_date` are physically repeated in both tables, so a schema change to a base-class column means altering every subclass table individually and keeping them in sync) and the loss of a natural place to enforce identity uniqueness across the whole hierarchy (two different subclass tables could theoretically reuse the same primary key value, since there's no shared sequence enforcing hierarchy-wide uniqueness unless deliberately set up). - **Polymorphic queries** across the hierarchy are the most awkward here: since there's no shared table, 'all employees' has to be a `UNION` (or `UNION ALL`) across every subclass table, projecting only the common columns. ## Choosing between them In practice, the choice tracks how deep and volatile the hierarchy is and how often polymorphic queries are needed versus subclass-specific ones. 1. **Single Table Inheritance** is the default in frameworks like Hibernate/JPA (`@Inheritance(strategy = SINGLE_TABLE)`) precisely because it's simplest and fastest for shallow hierarchies with few subclass-specific columns and frequent polymorphic access - a classic real-world example is a Payment hierarchy (CreditCardPayment, BankTransferPayment, PayPalPayment) in an e-commerce system, where reporting queries routinely need 'all payments' regardless of type. 2. **Class Table Inheritance** is favored when subclasses diverge significantly and normalization matters more than join cost, or when different subclasses are owned/evolved by different teams. 3. **Concrete Table Inheritance** is the least commonly chosen of the three in ORMs (it maps awkwardly to a single shared identity space) but shows up naturally in systems that never needed polymorphic queries in the first place and instead grew independent, subclass-specific tables that only later got treated as a conceptual hierarchy.

  • Why does Single Table Inheritance make it hard to enforce a NOT NULL constraint on a subclass-specific column?
    A NOT NULL constraint on hourly_rate would reject every Salaried and every other non-Hourly row, since that column is legitimately absent for them - the constraint would need to be conditional on the discriminator value, and standard column-level NOT NULL/CHECK constraints in most relational databases can't express 'required only when employee_type = HOURLY' without a more elaborate CHECK expression referencing multiple columns.
  • What happens to a polymorphic query ('all employees') under Class Table Inheritance versus Single Table Inheritance?
    Under Single Table Inheritance it's a plain SELECT over the one table, since every employee of every subclass already lives there. Under Class Table Inheritance it requires an outer join from the base employees table to every subclass table (or a UNION of per-subclass joined queries), since each subclass's extra columns live in a separate table that a given base row may or may not have a matching entry in.
  • How does adding a new subclass to the hierarchy affect each strategy differently?
    Single Table Inheritance just adds more nullable columns to the existing wide table and a new discriminator value - no new table needed. Class Table Inheritance adds one new subclass table with a foreign key back to the base table - contained and normalized. Concrete Table Inheritance adds one new fully self-contained table that duplicates every base-class column again, increasing the duplication and schema-sync burden with every new subclass.

Single Table Inheritance is like one giant form with every possible field for every job type, mostly blank per person. Class Table Inheritance is like a base ID card plus a separate specialist certificate you staple to it. Concrete Table Inheritance is like each job type having its own completely separate, fully filled-out form with no shared card at all.

saying these in an interview costs you the question

  • Can't name what a discriminator column is or its role in Single Table Inheritance
  • Thinks Class Table Inheritance avoids joins
  • Doesn't recognize that Single Table Inheritance forces subclass-specific columns to be nullable
  • Assumes Concrete Table Inheritance shares a base table
  • Can't explain why polymorphic queries are harder under Class/Concrete Table Inheritance than Single Table Inheritance

context