Solving the N+1 Query Problem in Laravel Eloquent
Stop your Laravel application from slowing down as your data grows. Learn how to identify and fix the N+1 query problem using Eloquent's eager loading techniques.
08 Jul 2025, 21:52 UTC

The Silent Performance Killer
You build a feature that works perfectly with five records in your database. But as soon as you move to production with five thousand, the page load time spikes. You check your logs and find hundreds of nearly identical SQL queries firing in a millisecond sequence. This is the classic N+1 query problem.
The problem occurs when you retrieve a collection of models and then access a relationship on each model inside a loop. Eloquent, by default, uses "lazy loading," meaning it doesn't fetch the related data until the moment you actually ask for it. If you have 100 posts and ask for each post's author, Laravel executes one query to get the posts, and then 100 individual queries to get the author for each post. That is 101 queries (N+1) to display a simple list.
Collapsing Queries with Eager Loading
The solution is Eager Loading using the with() method. Instead of waiting until the loop to fetch data, eager loading tells Eloquent to grab all the related records in one go immediately after the primary query.
When you use Post::with('author')->get(), Laravel performs two steps:
- It executes the primary query to get all posts.
- It collects all the
author_idvalues from those posts and executes a single query:SELECT * FROM authors WHERE id IN (1, 2, 3...).
Regardless of whether you have 10 posts or 1,000, the result is always exactly two queries.
Worked Example: From 101 Queries to 2
Consider a scenario where we display a list of blog posts and their authors. We assume Laravel 10.x or 11.x is being used.
The Inefficient Way (Lazy Loading)
// In your Controller
$posts = Post::all();
// In your Blade view
@foreach($posts as $post)
<p>{{ $post->title }} by {{ $post->author->name }}</p>
@endforeach
If you use DB::enableQueryLog(), you will see a sequence like this:
select * from postsselect * from authors where id = 1select * from authors where id = 2- ... (and so on)
The Efficient Way (Eager Loading)
// In your Controller
$posts = Post::with('author')->get();
// The Blade view remains exactly the same
@foreach($posts as $post)
<p>{{ $post->title }} by {{ $post->author->name }}</p>
@endforeach
The query log now shows only two entries:
select * from postsselect * from authors where id in (1, 2, 3, 4, 5...)
Advanced Loading Strategies
Sometimes you already have a collection of models before you realize you need their relationships. In these cases, use load() (Lazy Eager Loading). This is useful in conditional logic where you only need the relationship under certain circumstances.
$posts = Post::all();
if ($showAuthors) {
$posts->load('author');
}
To prevent these performance regressions from reaching production, you can disable lazy loading entirely in your AppServiceProvider. This will throw an exception during development whenever a relationship is lazy-loaded, forcing you to fix the N+1 issue immediately.
// app/Providers/AppServiceProvider.php
public function boot(): void
{
Model::preventLazyLoading(! app()->isProduction());
}
Trade-offs and Memory Constraints
Eager loading is not a magic bullet. Loading massive relationships into memory can lead to Allowed memory size exhausted errors. If a post has 10,000 comments, Post::with('comments')->get() will attempt to hydrate 10,000 model instances per post.
To mitigate this, use Constrained Eager Loading to limit the data returned:
Post::with(['comments' => function ($query) {
$query->latest()->limit(5);
}])->get();
Additionally, if you only need a specific field (like the author's name), specify the columns. Warning: You must always include the foreign key and the primary key in the selection, otherwise Eloquent cannot link the models together.
Post::with('author:id,name')->get();
Verification Checklist
To verify your fix, follow these steps:
- Install Laravel Debugbar or Telescope: These tools provide a real-time count of queries per request.
- Check the Query Count: Load the page and ensure the number of queries remains constant even as you add more records to the database.
- Test with
preventLazyLoading(): Enable this in your local environment; if the page loads without an exception, your eager loading is correctly implemented.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.