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
// 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
// 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
- What is APM? — how monitoring tools detect N+1s at scale
- Guide — detect N+1 queries in Laravel production
- Guide — monitor slow SQL queries in Laravel
- p95 vs p99 latency — why averages hide N+1 pain