Abdul Wahab

Blog/

Your API is fine. Your query log is not: the N+1 problem

One list query, then one query per row for related data , that is how a clean-looking ORM loop turns into 101 database round trips. Here is how to spot and fix N+1.

N+1 queries vs eager load / batched IN query diagram

Your API feels slow. The database is fine. The code looks fine. Then you open the query log and see 101 queries for a page that should need 2.

That is the N+1 problem.

You fetch a list once. Say 100 posts. That is the “1.” Then, somewhere in a loop, you ask for related data per item: comments, author, tags. That is the “N.” One query for the list, plus one query per row. With 100 posts, you just paid for 101 round trips to the database for something a join or a batch load could have done in two.

It shows up a lot with ORMs and “clean” looking code. Something like:

posts = await db.posts.findMany()
for (const post of posts) {
post.comments = await db.comments.findMany({ where: { postId: post.id } })
}

Readable. Also brutal at scale. Latency adds up per query, connection pool pressure goes up, and the page gets slower as the list grows. Ten items feel okay. A hundred does not.

How I usually fix it

  1. Eager load / include. Tell the ORM to fetch the relations in the same trip (Prisma include, Django select_related / prefetch_related, Rails includes, etc.).
  2. Batch with IN. One query for posts, one for all comments where post_id IN (...), then stitch in memory.
  3. Join when you only need a flat shape. One SQL statement can return posts plus the related rows you care about.

The rule of thumb: if you are hitting the database inside a loop over a list you just loaded, stop and ask whether that should be one batch instead.

How to catch it before users do

Turn on query logging in local or staging, load a realistic list size, and count. If the count climbs with the list length, you found an N+1.

This is not exotic architecture. It is one of those silent costs that looks like “the app got slower somehow” until you read the log.

Written by Abdul Wahab

← All posts