Solving Laravel's N+1 Query Problem with Eloquent Eager Loading
Learn how to spot and fix Laravel’s N+1 query problem using Eloquent’s with() method, with a concrete example, verification steps, and trade‑off guidance.
11 Jul 2026, 08:48 UTC

Why a simple loop can hammer your database
When you fetch a collection of models and then access a relationship inside a foreach loop, Laravel runs a separate query for each iteration if the relationship isn’t pre‑loaded. This pattern is known as the N+1 query problem: one query to fetch the primary models, plus N additional queries—one for each related record.
For example, displaying a list of blog posts with their comment counts without eager loading will execute:
- One query to get all posts.
- One query per post to fetch its comments.
With ten posts you end up with eleven queries, which quickly becomes a bottleneck as the data set grows.
Eager loading with with() cuts the query count
Eloquent’s with() method tells Laravel to load the specified relationship(s) in a single query per relationship, hydrating the related models up front. The result is a constant number of queries regardless of collection size: one query for the primary models and one query per eager‑loaded relationship.
Using the same posts‑and‑comments scenario, with('comments') reduces the workload to:
- One query to fetch all posts.
- One query to fetch all comments where the
post_idmatches the retrieved posts.
That’s just two queries, no matter if you have ten or ten thousand posts.
Worked example and verification steps
Place the following code in a controller method, a route closure, or Laravel Tinker (php artisan tinker). You need database read access; no special permissions are required beyond the usual application credentials.
use App\Models\Post;
use Illuminate\Support\Facade\DB;
// Enable the query log so we can inspect what ran
DB::enableQueryLog();
// ---- Without eager loading (N+1) ----
$posts = Post::take(10)->get(); // 1 query
foreach ($posts as $post) {
// Accessing comments triggers a query per post
$post->comments->count(); // N queries
}
$queriesWithout = DB::getQueryLog();
// Reset log for the next test
DB::flushQueryLog();
// ---- With eager loading (1+N) ----
$posts = Post::with('comments')->take(10)->get(); // 1 query for posts + 1 for comments
foreach ($posts as $post) {
$post->comments->count(); // No extra queries
}
$queriesWith = DB::getQueryLog();
// Output the counts (you can view these in your logs or dd() them)
echo "Queries without eager loading: " . count($queriesWithout) . "\n";
echo "Queries with eager loading: " . count($queriesWith) . "\n";
To verify the actual SQL, examine the query log:
dd($queriesWith);
You should see two SELECT statements similar to:
select * from `posts` limit 10select * from `comments` where `posts_id` in (?, ?, …)
No per‑post comment queries appear when with('comments') is used.
Trade‑offs and practical limits
Eager loading trades query count for memory consumption. All related models are hydrated up front, so if a relationship returns many columns or thousands of rows, the PHP process may use significantly more RAM. Over‑eager loading—adding with() to every query indiscriminately—can hide inefficiencies and degrade performance.
To mitigate:
- Select only the columns you need:
Post::with(['comments:id,post_id,body'])->get(). - Profile first: enable the query log or use Laravel Debugbar to confirm an N+1 pattern exists.
- Consider chunking or cursor iteration for very large collections to keep memory usage low.
Actionable closing
Start by identifying loops that access relationships. Run a quick query‑log check before and after adding with() to see the impact. If the query count drops and memory usage stays within acceptable limits, keep the eager load. If memory spikes, trim the selected columns or revert to lazy loading for that specific case. Regularly profiling your application’s database interactions will keep the N+1 problem at bay while preserving efficient resource use.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.