Skip to content
enes
2026-03-14engineering2 min read

100 items, 101 queries: the N+1 pattern and how to kill it.

N+1 shows up every time you put an `await` inside a `for`. How to spot it in your logs and swap it for a single query.

An endpoint lists 100 stores and takes 3 seconds. The database log shows 101 queries per request: one for the list of stores, one hundred more to count reviews. The user feels it as "slow today." The database feels it as CPU at 80%.

The pattern is old and called N+1. It shows up every time there's an await inside a for or forEach: one initial query fetches N items, and each item triggers one more query to "enrich it."

// ❌ N+1: 101 queries for 100 stores
const stores = await prisma.store.findMany()
for (const store of stores) {
  const reviews = await prisma.review.findMany({
    where: { storeId: store.id },
  })
  store.reviewCount = reviews.length
}

// ✅ One query, the ORM does the join + aggregation
const stores = await prisma.store.findMany({
  include: {
    _count: { select: { reviews: true } },
  },
})

Three seconds become 80 milliseconds. More importantly: the cost stops scaling with the data. 100 stores or 10,000, one single query.

N+1's cousin: sequential queries that could be parallel

The other pattern that looks similar without being the same: three awaits in a row that don't depend on each other.

// ❌ Sequential, total time = sum
const store = await getStore(id)          // 200ms
const products = await getProducts(id)    // 300ms
const analytics = await getAnalytics(id)  // 500ms
// Total: 1000ms

// ✅ Parallel, total time = the slowest one
const [store, products, analytics] = await Promise.all([
  getStore(id),
  getProducts(id),
  getAnalytics(id),
])
// Total: 500ms

Before optimizing CPU, kill network round-trips. Network latency is almost always the real bottleneck. One Promise.all can save more time than a week of micro-optimization.

How to spot it without reading code

Run the request and count the queries in the database log. If the query count grows with data volume, you have an N+1. If it's constant, you're fine.

It's the simplest performance audit for an endpoint you didn't write yourself.