The N+1 query: the most typical ORM performance trap
Fetch 100 rows and query the relation for each one and you have made 101 queries. There are only three fixes: eager load, batch fetch, or use a single join.
The N+1 shape is fixed: fetch a batch of parent rows, then query the relation once per row. N parents means N+1 round trips.
const posts = await db.post.findMany(); // 1 query
for (const post of posts) {
post.author = await db.user.findUnique({ // N queries
where: { id: post.authorId },
});
}
A hundred posts is 101 queries. Each is fast, but accumulated round-trip latency drags the page into seconds.
Three fixes
Eager loading (the ORM’s include / with). Most ORMs handle this in one statement:
const posts = await db.post.findMany({ include: { author: true } });
Internally the ORM usually issues two queries (one for posts, one WHERE id IN (...) for authors), not N+1. This is the first choice.
Manual batch fetch. Collect the foreign keys, fetch once, join in memory:
const posts = await db.post.findMany();
const ids = [...new Set(posts.map((p) => p.authorId))];
const authors = await db.user.findMany({ where: { id: { in: ids } } });
const byId = new Map(authors.map((a) => [a.id, a]));
for (const post of posts) post.author = byId.get(post.authorId);
A join. When the relation is simple and you do not need nested structures, one query is most direct. The cost is that parent rows multiply across the joined table and need deduplication.
How to find it
N+1 is invisible in development. A local database has no network latency, so 100 queries take milliseconds. Production is where it shows.
| Approach | How |
|---|---|
| Query log | turn on SQL logging, look for repeated statement shapes |
| APM | count queries per request, alert on a threshold |
| ORM hooks | assert “at most K queries per request” in tests |
The third works best: turn the query count into a test assertion and N+1 gets caught in CI instead of production.
When to ignore it
- The parent count has a hard cap (five at most, say)
- The relation comes from a cache and issues no query
- The endpoint is not on a critical path
Judge by the actual data size. A paginated list of 20 per page at 21 queries is usually fine, provided you do not nest another relation inside it.
N+1 is not slow because of one query; it is slow because of the round trips. Finding it takes a query count, fixing it takes one batch.

Comments
…