Eloquent Performance: N+1 Problem
Eloquent N+1 Problem: Tips & Tricks
Eloquent N+1 Problem: Tips & Tricks
Tip: Enable Lazy Loading Prevention
Model::preventLazyLoading(! app()->isProduction());
Throws an exception when you access an unloaded relationship.
Gotcha: with() Loads Everything
Post::with('comments')->get();
// Loads ALL comments, even if you only need 5
Use constraints:
Post::with(['comments' => fn($q) => $q->latest()->limit(5)])->get();
Tip: Use withCount() Instead of Counting
// Bad: N+1
foreach ($posts as $post) {
echo $post->comments->count();
}
// Good: 1 query
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
Tip: Selective Column Loading
Post::with('author:id,name,email')->get();
Only loads specific columns from the related model.
Gotcha: load() Runs After the Fact
$posts = Post::all(); // Query 1
$posts->load('author'); // Query 2
Use with() instead if you know you need the relation.
Tip: Use exists() for Presence Checks
// Bad
if ($post->comments->isNotEmpty()) { }
// Good
if ($post->comments()->exists()) { }
Tip: Debug with DB::listen()
DB::listen(function ($query) {
Log::info($query->sql);
});
See every query being executed.
Gotcha: pluck() Can Cause N+1 Too
$posts->pluck('author.name'); // N+1 if author not loaded
Tip: Use cursor() for Memory-Neutral Iteration
When exporting 100K rows, get() loads everything into memory. cursor() uses yield and keeps memory flat regardless of row count. Perfect for artisan commands.
Tip: whereHas() vs load() — Two Different Things
whereHas() filters the parent query by relationship existence. load() eager-loads relationships AFTER the query. Mixing them up is a common source of logic bugs.
Gotcha: touch() Fires Multiple Queries
Calling touch() on a model updates the updated_at of ALL related models in the relationship chain. This can cascade into dozens of unnecessary writes.
Senior Insight
The N+1 problem is the most common performance issue in Laravel applications, and it's so easy to prevent. Beyond using with(), I rely on load() for conditional eager loading — only load relationships when they're actually needed. I also use setEagerLoads() in API responses to let the client specify which relations to include. This approach eliminates the 'load everything just in case' pattern that bloats responses and slows applications.
Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)