Oksa Satya.

March 22, 2026 · 6 min read

Killing N+1 Queries: Why an App Is Fast in the Demo but Slow in Production

PerformanceDatabasePostgreSQLOptimization

An app feels instant on a laptop with 50 rows of seed data, then chokes when a production table hits tens of thousands. The culprit is often one simple pattern: the N+1 query. It slips through review because the code looks reasonable — and only bites as n grows.

What actually happens

The N+1 pattern appears when you fetch a list (1 query), then for each item fetch related data with one more query (N queries). Ten items = 11 queries; ten thousand items = 10,001 queries. On small data it's invisible; on real data it blows up linearly.

What makes it dangerous: the code is often written in a loop that looks clean, or hidden behind ORM lazy-loading. Nothing is 'wrong' syntactically — what's wrong is the number of round-trips to the database.

The fix: bound the round-trips, don't prettify the loop

  • Fetch related data in one query with a JOIN, or one IN query for all ids at once (two queries total, not N+1).
  • For many-to-many relationships, fetch in batches then map in memory (O(n)) instead of a query per item.
  • Make sure the filtered/joined columns have the right index — a composite matching the query order.
  • Measure by counting actual queries (logs/tracing), not by guessing. Queries-per-request is an honest metric.

Test at a realistic n

The root of the problem is often not the code but the data: tests and demos use an n too small to surface it. Test data-heavy paths at a size near production, and performance regressions get caught while they are still cheap to fix.

Rule of thumb: before writing a loop that touches the database, ask how many queries it will produce when n is large. If the answer grows with n, something needs batching.

Summary

N+1 is not an exotic bug but a default pattern that is easy to write unknowingly. Recognize its shape, bound round-trips with JOIN/IN plus the right index, and test at a realistic n. The difference between instant and timeout is often just the query count.

Building something similar?

I take on projects and technical consulting around business systems.