GAP·MAP
← all breakdowns
DatabasesORMsQuery PerformanceEager Loadingjunior level

One Query, Then One per Row

the question

What is the N+1 query problem, and how would you find and fix it in a backend application?

what they're testing: The interviewer is checking whether you can connect innocent-looking relationship access to the SQL it generates and replace per-row lookups with an appropriate loading strategy.

a strong answer

An N+1 query problem happens when the application runs one query for a list, then another query for each item to load related data. A page that fetches 100 orders and lazily loads each order's customer can issue 101 queries. Lazy relationship access inside a loop is a common trigger. The individual queries may be fast, but the database round trips grow with the list.

Fix it by loading related data for the whole collection. For a single-valued relation, eager loading with a join often works well. For a to-many relation, a batched prefetch can fetch the parents, fetch related rows with WHERE parent_id IN (...), and match them in memory. Common ORM APIs include select_related, prefetch_related, joinedload, and selectinload.

Do not eager-load every relationship by default. A to-many join repeats parent data across SQL result rows and can make pagination harder to reason about. Batched loading uses extra statements, and very large IN lists have costs of their own. Inspect the generated SQL or request trace, load only what the endpoint uses, and add a regression test showing that query count does not grow one-for-one with the number of parents.

Where people slip

the tempting wrong answer, and what's actually true

  • N+1 means one slow SQL statement with a subquery in it.

    N+1 refers to one initial query followed by separate queries for individual results; a single SQL statement may be slow, but it is not that query pattern.

  • Adding an index fixes an N+1 problem.

    An index may speed up each child lookup, but it does not remove the growing number of database round trips.

  • The fix is always to replace all the queries with one large join.

    A join is one option, but a batched prefetch can replace per-parent lookups with one or a few bulk queries and avoid the repeated parent data produced by a to-many join.

  • Relationships should always be eager-loaded by default.

    Eager loading unused relationships can fetch unnecessary data, so loading should be shaped around what the request will actually access.

If they push further

How would you detect an N+1 in production or a test?

Inspect query logs or tracing for the same statement repeated with different IDs, and track query count per request. A regression test can compare small and large fixtures and reject query counts that grow one-for-one with the data.

When would you choose a join over a batched prefetch?

Join eager loading is a natural fit for many-to-one or one-to-one data. A batched prefetch is often better for to-many data because it avoids multiplying SQL result rows. Filtering on related rows may require a join regardless of the loading strategy.

Can GraphQL resolvers create the same problem?

Yes. A field resolver can perform one lookup per parent, so request-scoped batching groups those keys into a small number of bulk queries while preserving resolver boundaries.

Sources

Now answer it yourself.

Reading a strong answer is easy. Producing one under pressure is the skill the interview tests. Gapmap grades your answer against the same bar an interviewer would.

was this useful?

beta

The interviewer part is in the works.

The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.