Bilge
3 min read Tom Shafer

What is an N+1 query?

An N+1 query is a performance bug where code runs one query to fetch a list, then one more query per item — turning a list of N into N+1 queries. Here's why, and the fix.

An N+1 query is a common performance bug where an application runs one query to load a list of records, then runs an additional query for each record in that list — so fetching N items costs N+1 queries instead of 1 or 2. It's the single most common database performance problem in apps built on ORMs.

It's called "N+1" because of the arithmetic: 1 query for the list, plus N queries (one per item). Ten items, eleven queries. A thousand items, a thousand and one.

A concrete example

Here's the classic shape, in a Laravel/Eloquent app:

$posts = Post::all();          // 1 query: SELECT * FROM posts

foreach ($posts as $post) {
    echo $post->author->name;  // 1 query PER post: SELECT * FROM authors WHERE id = ?
}

The first line is one query. The loop then fires a separate query for every post's author. The same thing happens in Rails (has_many/belongs_to), Django, Hibernate, and any ORM with lazy-loaded relationships.

Why N+1 queries happen

ORMs make relationships feel like plain object properties: $post->author looks like reading a field, but it secretly runs a query the first time you touch it. This is lazy loading, and it's convenient — right up until you do it inside a loop. Each iteration triggers another round trip to the database.

The reason it's so easy to miss: it usually works fine in development. With ten rows of seed data, eleven queries is imperceptible. In production, with thousands of rows, those thousands of queries turn an instant page into a multi-second one — and nobody changed the code.

The fix: eager loading

The fix is almost always eager loading — telling the ORM to fetch the related records up front, in one extra query, instead of lazily per item:

$posts = Post::with('author')->get();  // 2 queries total, regardless of N

foreach ($posts as $post) {
    echo $post->author->name;          // no extra query
}

Two queries instead of a thousand-and-one. The data is the same; you've just told the ORM to batch the relationship fetch.

How to catch them

You can find N+1s by hand with a query log or a debug bar in development — but they creep back in every time someone adds an innocent ->relationship inside a loop, and they only bite at production scale. The durable fix is to have something watch production and flag the pattern for you.

On Laravel, Bilge flags N+1 queries automatically — no configuration — showing you the repeated query, how many times it ran, and which request to fix. It's part of the broader picture in the complete guide to Laravel error tracking.

Spin up a free project and find the N+1 that's already slowing down your busiest page.

performance n-plus-1 databases glossary