In Django's ORM, what do QuerySet.only() and defer() do, and what does reading a deferred field cost you later?
answer
- still model instances, fewer columns
- one replaces, one adds
- primary key is never deferred
- a query per instance per field
basics
~20 sdefer() leaves named columns out of the SELECT and only() loads just the named ones; you still get model instances. Reading a skipped field later runs one extra query for that instance under the default FETCH_ONE mode.
solid answer
~40 s`defer("description")` drops columns from the `SELECT`; `only("id", "title", "status")` keeps just those. Both return normal model instances, which is the point compared with `values()`. The primary key is always loaded. `defer()` calls **add** to the deferred set and `defer(None)` clears it; each `only()` call **replaces** the loaded set. The cost comes later: touching a deferred field calls `refresh_from_db(fields=[...])` for that one instance, so a loop that reads `ticket.description` on 50 tickets adds 50 queries under the default `FETCH_ONE` mode (Django 6.1's `FETCH_PEERS` batches them into one query per field). Other edges: `save()` on such an instance writes only the loaded fields, deferred fields raise `SynchronousOnlyOperation` in async code instead of loading, and combining `only()` with `select_related()` must keep the foreign key or Django raises a `FieldError`.
code
python · 12 linesfrom helpdesk.models import Ticket
tickets = Ticket.objects.only("id", "title", "status") # description not selected
for t in tickets:
print(t.title) # loaded, no query
print(t.description) # deferred: one extra SELECT per ticket (FETCH_ONE)
t = tickets[0]
t.get_deferred_fields() # {'description', 'assignee_id', ...}
t.status = "closed"
t.save() # UPDATE writes only the loaded fieldsgo deeper
Recall that only() and defer() still return model instances with fewer columns loaded, and that the primary key is always included.
Explain additive defer() versus replacing only(), the per-instance refresh a deferred read costs, and the save() and select_related() rules.
Show when trimming columns pays off, how a template or serializer reading an extra field silently undoes it, and what to use instead.
Weigh ad hoc only() calls against modelling fixes such as splitting wide columns into their own table or an unmanaged read model.
## What the two methods change Every Django `QuerySet` for a model normally selects **all concrete columns** of that model. Two methods trim that list while still returning **model instances**: - **`defer(*fields)`** — load everything *except* the named fields. `Ticket.objects.defer("description", "attachments_json")`. - **`only(*fields)`** — load *only* the named fields (plus the primary key). `Ticket.objects.only("title", "status", "assignee")`. Both are useful when a model has a wide column — a long `TextField`, a large `JSONField`, a binary blob — that a list page never shows. ## How calls combine | Call | Effect | |---|---| | `defer("a").defer("b")` | both `a` and `b` deferred — `defer()` is **additive** | | `only("a", "b").only("c")` | only `c` loaded — each `only()` **replaces** the set | | `only("a", "b").defer("b")` | only `a` loaded | | `defer(None)` | clears the deferred set; everything loads | | `only()` with no arguments | loads every field, annotations included | Rules that always hold: 1. **The primary key is never deferred.** Django needs it to identify the instance and to fetch the rest later. 2. **Annotations are always fetched.** Naming an aggregate in `only()` or `defer()` raises an error. 3. **`select_related()` needs the link.** `Ticket.objects.select_related("assignee").only("title")` raises `FieldError: Field Ticket.assignee cannot be both deferred and traversed using select_related at the same time.` Include it — `only("title", "assignee__username")` loads the ticket's title, the foreign key and the joined user's username. ## The deferred-field cost A deferred field is not missing; it is **loaded on demand**. When code reads `ticket.description` and the value is not in the instance's `__dict__`, Django's field descriptor asks the instance's **fetch mode** what to do: - **`FETCH_ONE`** (the default) calls `refresh_from_db(fields=["description"])` — **one `SELECT` for that instance and that field**. Loop over 50 tickets and read two deferred fields, and you have added 100 queries. - **`FETCH_PEERS`** (6.1) loads that field for every instance from the same QuerySet in **one query per field**. - **`FETCH_RAISE`** (6.1) raises `FieldFetchBlocked` instead of querying. `instance.get_deferred_fields()` returns the set of attribute names not yet loaded, which is handy in tests and debugging. ## Side effects beyond reading - **Saving.** Calling `save()` on an instance with deferred fields automatically becomes an `update_fields` save of the **loaded** fields only, so a deferred column is not overwritten with a stale value. - **Async code.** Deferred fields do not lazy-load from asynchronous code; you get `SynchronousOnlyOperation`. List every field an async path touches in `only()`. - **Unsaved instances.** Reading a deferred field on an instance without a primary key raises `AttributeError`, because there is nothing to fetch by. ## Diagnosing a deferred-field N+1 The symptom looks like any per-row query storm, but the SQL gives it away: many near-identical `SELECT "helpdesk_ticket"."id", "helpdesk_ticket"."description" FROM "helpdesk_ticket" WHERE "helpdesk_ticket"."id" = %s` statements, each re-reading **one column of the same table** by primary key. Steps that find the cause quickly: 1. Find the QuerySet that built the instances and look for `only()` or `defer()` — often added in a manager or a shared helper, far from the template. 2. List what the consumer (template, serializer, export) actually reads, and compare it with the loaded fields. 3. Fix by adding the field to `only()`, removing the trim, or — on 6.1 — using `FETCH_PEERS` so the leftovers cost one query per field instead of one per row. 4. Re-count the queries; a deferred-field fix should leave exactly one `SELECT` for the table. Because the extra queries hit the **same table** by primary key, they are easy to mistake for harmless lookups; on a list of hundreds of rows they are not. ## When to use it — and when not The documentation is blunt: these methods are for **advanced use**, after measuring that the skipped columns matter. Guidance worth repeating in an interview: - Use `defer()` when you **cannot know at query time** whether a wide field will be needed, and it usually is not. - If a page always needs the same small subset, `only()` is fine, but consider whether you need instances at all — `values()`/`values_list()` skip instance construction entirely. - If a subset is used everywhere, the docs suggest **moving the wide data to its own model**, or an unmanaged model (`Meta.managed = False`) over the same table with just the common fields. - Never add `only()` to a queryset and then pass it to code — a template, a serializer — that reads other fields. That turns one query into N+1 quietly.
- What does save() write for an instance loaded with only("title", "status")?Django notices the instance has deferred fields and turns the save into an `update_fields` save of the loaded fields only — here `title` and `status`. Deferred columns are left untouched in the database, so a partial load cannot overwrite them with missing values.
- How do only() and select_related() work together?They combine, but the foreign key being joined must stay loaded. Use the double-underscore form to trim the related model too: `select_related("assignee").only("title", "assignee__username")`. Leaving the key out raises `FieldError` saying the field cannot be both deferred and traversed.
- What happens when async Django code reads a field excluded by only()?Deferred fields do not lazy-load from asynchronous code; the access raises `SynchronousOnlyOperation` instead of querying. Async paths must name every field they read in `only()`, or avoid trimming columns for that QuerySet.
saying these in an interview costs you the question
- Says only() returns dictionaries rather than model instances
- Believes a second only() call adds to the first
- Thinks reading a deferred field raises an error by default
- Claims the primary key can be deferred
- Assumes save() on a partially loaded instance writes every column