[ GLOSSARY ]

What is an N+1 Query Problem?

QUICK ANSWER

What is an N+1 query?

An N+1 query is a performance anti-pattern where loading a list of N parent records triggers N additional queries — one per parent — to fetch a related record for each. Instead of 2 efficient queries (parents + batched children), you fire 1 + N queries, producing linear performance degradation as the list grows.

Updated · 2026-06-04

The N+1 is the most common reason a Laravel page is slow, and one of the easiest to ship without noticing. The code looks innocent: a loop over some models, reaching for a relationship inside it. Eloquent makes that line so natural to write that you rarely think about the query it fires. Multiply it by the number of rows on the page and a fast endpoint quietly becomes a slow one.

Why the name?

The "1" is the initial query that returns a list of N rows. The "N" is the N follow-up queries — one per row — to load related data. For 50 posts loaded with their author, that's 51 total queries instead of the 2 you actually need.

The classic Laravel example

N+1 — fires 1 + 50 = 51 queries

php
// 1 query: SELECT * FROM posts ORDER BY ... LIMIT 50
$posts = Post::latest()->take(50)->get();

foreach ($posts as $post) {
    // 50 queries: SELECT * FROM users WHERE id = ?
    echo $post->author->name;
}

Fixed — fires 2 queries

php
// 2 queries: 1 for posts, 1 batched for all authors
$posts = Post::with('author')->latest()->take(50)->get();

foreach ($posts as $post) {
    echo $post->author->name; // already in memory
}

Why it matters

Every query has network and protocol overhead — typically 1-5ms on a well-tuned system. The queries themselves might be fast, but 50 round-trips stack up: a page that should run in 50ms runs in 250-500ms. Scale that to 500 items and you're in seconds territory.

N+1 is the single most common cause of slow Laravel endpoints. Finding and fixing one often cuts a page's response time by 5-10x.

Where they come from

Almost always a relationship accessed inside a loop — but the loop isn't always where you'd look. A few places they hide:

In Blade. @foreach ($posts as $post) {{ $post->author->name }} is an N+1 even though there's no visible query in your controller — the relationship resolves lazily when the view renders.

In API resources. A PostResource that returns $this->author->name fires one query per item in the collection, and the loop lives inside Laravel's serialization where you never see it.

After a refactor. Someone adds with('author') when they write the endpoint. A later change adds a second relationship ($post->comments) and only the first stays eager-loaded. It passed review and degrades in production as data grows. That's the case worth monitoring for, because code review won't catch it twice.

How to detect N+1 in production

Model::preventLazyLoading() catches them in development. In production you need an APM that groups repeated query fingerprints per request — the signal is "the same normalized SQL fired 47 times in one request."

NightOwl gives you the data to spot it without auto-flagging: the query watcher fingerprints every statement and exposes a call_count column on the Queries page. The request detail page lists every query the request fired, so a repeated pattern is obvious at a glance — but the diagnosis is yours, not an automated threshold.

RELATED

Frequently asked questions

Why is it called N+1?

The '1' is the initial query that returns a list of N parent records. The 'N' is the N additional queries — one per parent — to load a related record for each. Total: 1 + N. For a list of 50 items, that's 51 queries instead of the 2 you actually need.

Is an N+1 always a bug?

Practically yes, though the severity varies. In a 5-item list it's a small waste. In a 500-item list it's the difference between a 50ms page and a 5-second page. Database round-trips dominate query latency — each trip is cheap, 500 trips in a loop are not.

How much slower is an N+1 query?

Each database round-trip costs 1-5ms of pure network/protocol overhead on top of execution. A 100-item list with an N+1 adds 100-500ms of latency — often 10x slower than the fixed version, even if the queries themselves are fast.

Does eager loading always fix N+1 queries?

Yes, but choose the right variant. with() on the query, load() after the fact, loadMissing() for conditional loads, morphWith() for polymorphic relations. For column-selection use with('author:id,name'). For nested relations use dotted syntax: with('posts.comments').

PRICING

Flat pricing. No event caps. No per-seat fees.

14-day free trial, no credit card. Your PostgreSQL, your data.

HOBBY

$5 /month

1 app · 14 days lookback · all Laravel events

TEAM

$15 /month

Up to 3 connected apps · unlimited environments · all Laravel events

AGENCY

$69 /month

Unlimited apps · unlimited agent instances · same flat rate at any traffic