N+1 queries are a symptom, not the disease
·2 min read ·Databases · Laravel · Performance
N+1 is probably the most commonly mentioned performance problem in Laravel applications. It's usually fixed with a quick with() call and forgotten. But fixing the symptom without understanding the disease means you'll keep writing it.
What N+1 Actually Is
You fetch 100 users. For each user, the ORM fires another query to get their profile. That's 1 + 100 = 101 queries. The number scales linearly with your dataset. What works fine in development with 10 seed records destroys production with 50,000 rows.
// This fires N+1 queries:
$users = User::all();
foreach ($users as $user) {
echo $user->profile->bio; // Query per iteration
}
// This fires 2 queries:
$users = User::with('profile')->get();
The Real Disease: Not Reading What Your ORM Generates
The with() fix is real. But if you don't know what SQL your code is generating, you'll write N+1 again next week without realizing it. The problem is blind trust in the abstraction.
Install Laravel Debugbar in development. Look at the query log. When you see the same query repeating with different IDs, you have an N+1.
Nested Relationships Are Trickier
// Still has N+1 for posts:
$users = User::with('profile')->get();
foreach ($users as $user) {
foreach ($user->posts as $post) { // N+1 again
echo $post->title;
}
}
// Fix:
$users = User::with(['profile', 'posts'])->get();
When Eager Loading Isn't the Answer
If you're loading a relationship just to count records, withCount() is more efficient. If you only need one attribute, consider a join or a raw select. Eager loading pulls entire models—sometimes that's more data than you need.
Understand what's happening under the hood. The ORM is convenient but it's not magic. Read the queries it generates.