skip to content

Table Inheritance Styles

Django's three inheritance styles: abstract base classes, multi-table inheritance with its implicit OneToOne join, and proxy models. Interviewers ask which to choose and what each costs per query.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

6

In Django, how do you choose between abstract base, multi-table and proxy model inheritance for a car-rental fleet's vehicle types?

level: middleimportance: must knowfreq 62%

answer

  1. what must exist in the database
  2. different columns or only behaviour
  3. does anything reference 'any vehicle'
  4. JOIN per query versus separate tables

basics

~20 s

Choose by what the database must hold: an abstract base gives each vehicle type its own complete table, multi-table inheritance keeps a queryable Vehicle table joined one-to-one to each child, and a proxy changes behaviour on one existing table without new columns.

solid answer

~40 s

Two questions decide it: do the types need different columns, and must anything treat "any vehicle" as one set? If types differ in columns but nothing references a generic vehicle, an abstract `VehicleBase` gives `Car`, `Van` and `Motorbike` their own complete tables with no JOINs. If a `Booking` needs one `ForeignKey(Vehicle)` and the fleet is listed as a whole, multi-table inheritance gives a real `vehicle` table with each child linked by an implicit `vehicle_ptr` one-to-one key, at the cost of a JOIN on every child read and two rows per save. If the difference is behaviour only (ordering, methods, a default manager, a separate admin), a proxy (`Meta.proxy = True`) reuses the table. A common alternative to a deep multi-table tree is one concrete `Vehicle` with a `kind` field plus proxies.

code

python · 27 lines
python
from django.db import models


class Audited(models.Model):  # abstract: columns copied, no table
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        abstract = True


class Vehicle(Audited):  # concrete parent: bookings can point here
    plate = models.CharField(max_length=12, unique=True)
    daily_rate = models.DecimalField(max_digits=7, decimal_places=2)


class Van(Vehicle):  # multi-table child: van table + vehicle_ptr_id
    cargo_volume_m3 = models.DecimalField(max_digits=5, decimal_places=2)


class ReturnedVan(Van):  # proxy: same van table, different behaviour
    class Meta:
        proxy = True
        ordering = ["-created_at"]


class Booking(models.Model):
    vehicle = models.ForeignKey(Vehicle, on_delete=models.PROTECT)

go deeper

for a junior

Know that there are three styles and the one-line storage shape of each: no base table, a parent table plus child tables, or the same table.

for a middle

Answer with the two deciding questions, different columns and a generic reference, and name the JOIN that multi-table inheritance adds to every child read.

for a senior

Bring the regret stories: bulk_create refusing multi-table children, no automatic downcast, and when a single concrete model with a kind field beats a multi-table tree.

for a principal

Weigh schema purity against runtime cost and team clarity, and decide who approves a new concrete parent before a hierarchy grows levels that every query must join.

## Three styles, three storage shapes Django supports three ways for one model class to inherit from another. They look almost identical in Python — `class Car(Vehicle):` — and differ entirely in what they create in the database: | Style | How you declare it | Tables | Query the base? | Base as `ForeignKey` target? | Cost per child read | |---|---|---|---|---|---| | **Abstract base class** | `Meta.abstract = True` on the base | one per concrete child | no — the base has no manager | no | none: one table | | **Multi-table inheritance** | subclass a concrete model | parent table + one per child | yes — returns parent instances | yes | an `INNER JOIN` to the parent | | **Proxy model** | `Meta.proxy = True` on the child | none new: the parent's table | yes | yes | none: same table | Django has **no** built-in single-table mode with a discriminator column; if you want one table for several kinds you model it yourself with a concrete model and a type field. ## The two questions that decide 1. **Do the subtypes need different columns?** A van has `cargo_volume_m3`, a car has `seats`, a motorbike has `engine_cc`. If yes, a proxy is out: a proxy may not declare fields. 2. **Must anything treat all vehicles as one set?** A `Booking` with a single `ForeignKey` to "whatever was rented", a fleet list paginated across types, a unique `plate` across the whole fleet. If yes, you need a concrete table that holds every vehicle, which an abstract base does not create. ## Applying it to the fleet - **Types differ, nothing generic references them** → abstract base. `VehicleBase` holds `plate`, `daily_rate` and `branch`; `Car`, `Van` and `Motorbike` each get a self-contained table. Reads are single-table, but "all vehicles at branch 12" means three queries, and a booking needs three nullable foreign keys or a generic relation. - **Types differ and bookings must point at any vehicle** → multi-table inheritance. `Vehicle` is concrete; `Car(Vehicle)` gets a `car` table whose primary key `vehicle_ptr_id` is also a foreign key to `vehicle.id`. `Booking.vehicle = ForeignKey(Vehicle, …)` works for every type. - **Same columns, different behaviour** → proxy. `ElectricCar(Car)` with `proxy = True` can sort by range, add `needs_charge_before()`, and get its own admin screen, all on the `car` table. ## Costs that show up later Multi-table inheritance is the style most often regretted, because its costs are invisible in the class definition: - every query that loads `Van` objects joins `van` to `vehicle`; deep trees join once per level; - `Vehicle.objects.all()` returns plain `Vehicle` objects — reaching van data needs `vehicle.van` (another query) or `select_related("van")`; - each save writes one row per level, and `bulk_create()` refuses multi-table children; - adding a relation from a subclass back to the parent needs an explicit `related_name`, because the implicit link already claims the default name. Abstract bases cost nothing at runtime but push "all vehicles" logic into application code. Proxies cost nothing but cannot add data. ## Questions to ask before committing Before choosing, walk through the queries and writes the fleet actually runs: 1. **Which screen lists several types together?** If the branch dashboard pages through cars and vans in one table, an abstract base forces a merge in Python; multi-table inheritance or one concrete table serve it in one query. 2. **Which code reads one type deeply?** A van-loading tool that filters on `cargo_volume_m3` pays a JOIN per query under multi-table inheritance and none under an abstract base. 3. **How are vehicles imported?** Large bulk imports argue against multi-table inheritance, because `bulk_create()` refuses multi-table children. 4. **How often do types gain columns?** Each new type under an abstract base is a new table and new listing code; under one concrete table it is more nullable columns. When the answers conflict, the reference and listing needs usually win, because a missing concrete parent is the hardest thing to add later. ## A common hybrid A frequent production shape combines them: an abstract mixin for audit columns, **one concrete `Vehicle`** with a `kind` choice field and the type-specific columns made nullable (guarded by conditional check constraints), and a **proxy per kind** carrying behaviour and, through a custom default manager, the filter on `kind`. That keeps one table, one foreign key target and no JOINs, and trades it for nullable columns. Choose multi-table inheritance when the subtype columns are many, mostly non-null and genuinely different — and when the JOIN is acceptable on every subtype query.

  • Bookings must reference any vehicle, but you want to avoid multi-table JOINs; what else works?
    Use one concrete `Vehicle` model with a `kind` choice field and nullable type-specific columns, guarded by check constraints conditioned on `kind`, plus a proxy per kind for behaviour. That is one table and one `ForeignKey` target with no JOIN; the trade is nullable columns and per-kind validation in code and constraints.
  • Can one Django hierarchy mix the three styles?
    Yes. An abstract mixin can feed a concrete parent, a multi-table child can extend that parent, and a proxy can sit on the child. The rules still apply per class: a proxy needs exactly one concrete base and no fields, and an abstract base supplies columns only to concrete descendants.

saying these in an interview costs you the question

  • An abstract base lets you query every vehicle type through one manager.
  • A proxy model is the right choice when a subtype needs extra columns.
  • Multi-table inheritance is free because Django caches the parent row.
  • Querying Vehicle returns Car and Van objects automatically where rows match.
  • Django has a built-in single-table inheritance mode with a discriminator column.
open as a page

In a Django model, what does setting abstract = True in the inner Meta class do, and when would you use it?

level: juniorimportance: should knowfreq 55%

basics

~20 s

It makes the model an abstract base class: Django creates no table and no manager for it, and every concrete subclass inherits its fields, managers and (unless overridden) its Meta in a complete table of its own.

open as a page

In Django, what can a proxy model declared with Meta.proxy = True change about its parent model, and what may it not add?

level: middleimportance: should knowfreq 42%

basics

~20 s

A proxy model reuses its parent's table and columns but may change Python-side behaviour: methods, str, default ordering, the default manager, and a separate admin and permissions. It may not add fields and must have exactly one concrete base.

open as a page

A Django fleet app stores Car and Van as multi-table children of Vehicle, and bulk imports, deletes and row locks misbehave; which write costs does multi-table inheritance add?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Each child save writes one row per level inside a transaction, bulk_create() raises ValueError for multi-table children, bulk_update() adds a query per ancestor, deleting a child also deletes its parent row unless keep_parents=True, and select_for_update(of=...) must name the parent link.

open as a page

In Django, which Meta options does a child model inherit from an abstract base class, and which from a multi-table parent?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

A child of an abstract base inherits the whole Meta if it declares none, with abstract reset to False, and can extend it by subclassing Base.Meta; a multi-table or proxy child inherits only ordering and get_latest_by.

open as a page