← All articles

Backend & Data

The N+1 query problem

2026-07-07 · 5 min read

You write an innocent loop: fetch a list of orders, then for each order show the customer's name. It's clean, readable, and passes every test — because your test database has five orders. In production, that list has 500, and your one page just fired 501 separate database queries. This is the N+1 problem, and it's probably the most common performance bug in web apps.

Where the extra queries come from

One query gets the list of N rows. Then, as your code loops over them, each iteration triggers another query to fetch related data — N more. That's 1 + N. Each query is fast on its own, but every one is a round trip to the database: send, wait, receive. Do that 500 times in a row and the waiting dominates. It's death by a thousand fast queries.

N+1 queries versus a single join N+1 · a query per row App DB get list row 1 row 2 … 1 + N round trips · slow One query · a join App DB get list + details 1 round trip · fast
Same result, two very different costs: hundreds of round trips versus one query that fetches everything together.

Why it hides so well

It usually isn't code you wrote on purpose. An ORM (the library that turns objects into SQL for you) makes order.customer.name look like a harmless property access — but each one secretly runs a query. The convenience is the trap: the queries are invisible in the code, and invisible on small test data.

The gist

Don't ask the database 500 tiny questions in a loop. Ask it one big question that brings back everything you need at once.

The fix

  • Fetch related data up front — a JOIN, or your ORM's "eager loading" / "include" — so it comes back with the list in one trip.
  • Batch the follow-ups. If you must do a second query, load all the related rows in a single WHERE id IN (…) instead of one per row.
  • Watch the query log in development. If one page fires dozens of near-identical queries, you've found an N+1.

It pairs with the other classic database lever: make sure the columns you're joining and filtering on are indexed. Fix the N+1 and add the index, and a page that took eight seconds often comes back in twenty milliseconds.


DatabasesPerformanceORMSQL
← Back to the blog