Single table inheritance: one table for a class hierarchy
See how a discriminator maps several entity types to one table, then decide when sparse columns and schema coupling outweigh simple queries.
Single table inheritance stores every subtype in one relational table. A discriminator column tells the ORM which class to construct; subtype-only columns are nullable. Reads avoid joins, but the table accumulates sparse columns and constraints become harder to enforce.
Imagine one filing cabinet for every kind of employee. A label on each folder says engineer or salesperson. Finding anyone is easy, but the cabinet needs fields that make sense only for one group, leaving many blanks.
Single table inheritance (STI) is a way to persist an object hierarchy in a relational database. It is easiest to see with a concrete model rather than with inheritance terminology. Suppose Payment has two subtypes: CardPayment, which has a card network, and BankTransfer, which has a bank reference. STI puts both in one payments table.
One table, many types#
CREATE TABLE payments (
id bigint PRIMARY KEY,
amount_cents bigint NOT NULL,
type text NOT NULL CHECK (type IN ('card', 'bank_transfer')),
card_network text,
bank_reference text,
CHECK (
(type = 'card' AND card_network IS NOT NULL AND bank_reference IS NULL)
OR
(type = 'bank_transfer' AND bank_reference IS NOT NULL AND card_network IS NULL)
)
);
The type value is the discriminator. When an ORM reads a row marked card, it constructs a CardPayment; bank_transfer produces a BankTransfer. Shared fields, such as amount_cents, occupy the same columns for both. The subtype fields must allow nulls at the column level because a card has no bank reference and a transfer has no card network. The conditional CHECK restores the rule for each subtype.
What it buys and costs#
A query across all payments reads one table. There is no join to recover subtype fields, and adding a new instance needs one insert. That simplicity is why STI is attractive when subtypes are few and genuinely share most of their data.
The cost grows with the hierarchy. Each subtype adds columns that are blank for other types. More significantly, a plain NOT NULL constraint cannot express “required for cards, absent for transfers”; it needs a conditional check like the one above. A new subtype changes a table used by every existing subtype. If the hierarchy changes frequently, that shared schema becomes a deployment coordination point.
Sparse columns alone are a poor reason to reject STI: databases often store nulls efficiently. The stronger questions are whether subtype-specific invariants remain enforceable and whether teams can change one subtype without repeatedly changing the shared table.
Choosing an inheritance mapping#
Use STI when the variants share many fields, cross-type queries are common, and their schema changes together. Consider joined inheritance when each subtype has many distinct fields and stronger subtype tables are useful: shared columns live in a base table, with subtype columns in separate tables joined by the same key. Consider concrete tables when types rarely need to be queried together and independent schemas matter more than a unified table.
This is a persistence decision, not a reason to force inheritance into the domain model. If the supposed subtypes have unrelated behavior, a class hierarchy may be the wrong starting point. The abstract class versus interface choice is about code reuse and contracts, not how rows must be stored. The same question about where a boundary belongs appears in transaction boundaries: choose the shape that protects the actual invariants.
Reference: Martin Fowler’s original pattern catalog and the Hibernate ORM inheritance mapping guide.
Questions people also ask
2 QUESTIONSWhat is a discriminator column?
A column such as type with values like card or bank_transfer. The ORM reads it to decide which subclass to instantiate from a row.
Does single table inheritance require an ORM?
No. The table design works without one, but ORMs make the mapping between a discriminator and a class hierarchy convenient. The database still has to enforce the relevant constraints.