Eloquent Eager Loading — Solving the N+1 Problem in Laravel
In this tutorial, you will learn about Eloquent Eager Loading. We cover key concepts, practical examples, and best practices to help you master this topic.
Laravel Eloquent eager loading prevents N+1 query problems by loading related models in advance with with(), load(), and loadMissing() methods.
What You'll Learn
By the end of this tutorial, you'll use with() to eager load relations, constrain eager loads, lazy eager load with load(), select specific columns, and prevent N+1 automatically.
Why Eager Loading Matters
N+1 queries are the most common performance problem in Laravel applications. Eager loading reduces database queries and eliminates performance degradation from loop-triggered Lazy Loading.
Real-World Use
A blog index page lists 50 posts with their authors and comment counts. Without eager loading, the page makes 1 + 50 + 50 = 101 queries. With eager loading, it makes 3 queries.
Eager Loading Path
flowchart LR
A[Eloquent ORM] --> B[Eager Loading]
B --> C[Pre-Load]
B --> D[Lazy Load]
B --> E[Constrained]
B --> F[Forced Prevention]
B --> G{You Are Here}
style G fill:#f90,color:#fff
Basic Eager Loading
Load relationships upfront.
<?php
// Without eager loading (N+1 problem)
$posts = Post::all(); // 1 query
foreach ($posts as $post) {
echo $post->author->name; // N queries
}
// With eager loading
$posts = Post::with("author")->get(); // 2 queries
foreach ($posts as $post) {
echo $post->author->name; // No additional queries
}
// Multiple relations
$posts = Post::with(["author", "comments", "tags"])->get();
// Nested relations
$posts = Post::with("author.profile", "comments.user")->get();
Lazy Eager Loading
Eager load relationships on existing models.
<?php
// After initial query
$posts = Post::all();
$posts->load("author");
$posts->load(["comments" => fn($q) => $q->where("approved", true)]);
$posts->loadMissing("author");
// Single model
$post = Post::first();
$post->load("author", "tags");
$post->loadCount("comments");
Constrained Eager Loading
Apply conditions to eager loaded relations.
<?php
use Illuminate\Database\Eloquent\Builder;
$users = User::with(["posts" => function (Builder $query) {
$query->where("published", true)
->orderBy("created_at", "desc")
->limit(5);
}])->get();
// With column selection
$users = User::with(["posts:title,slug,created_at,user_id"])->get();
$users = User::with(["posts" => function (Builder $query) {
$query->select("id", "title", "user_id", "created_at")
->where("published", true);
}])->get();
Eager Load Counts
Count relations without loading models.
<?php
$posts = Post::withCount("comments")->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
// Multiple counts
$posts = Post::withCount(["comments", "likes", "views"])
->withCount(["comments as approved_comments" => fn($q) => $q->where("approved", true)])
->get();
// Conditional counts
$posts = Post::withCount(["comments", "comments as pending_comments" => fn($q) => $q->where("status", "pending")])->get();
Preventing N+1 Automatically
Use N+1 detection packages or strict mode.
<?php
// In App\Providers\AppServiceProvider
use Illuminate\Database\Eloquent\Model;
public function boot(): void {
Model::preventLazyLoading(!$this->app->isProduction());
// Or always throw exceptions
Model::preventLazyLoading(true);
}
// This throws an exception when lazy loading happens
// Comment out preventLazyLoading in production after optimization
Common Mistakes
1. Eager Loading Without Using the Relation
Loading a relation that is never accessed wastes resources.
2. Not Selecting Foreign Keys in Constrained Loads
When selecting specific columns, include the foreign key. Otherwise Eloquent cannot match related models.
3. Over-Eager Loading
Loading many relations in a single query creates large joins. Consider lazy eager loading for secondary relations.
4. Eager Loading Counts When Not Needed
withCount adds a subquery. Only load counts when using them.
5. Nested Eager Loading Without Constraints
Deep nested relations load everything. Use constraints to limit depth and size.
Practice Questions
1. What is the N+1 Problem?
Loading the parent model and then lazy loading a relation in a loop, causing N additional queries.
2. How does with() prevent N+1?
It loads all related models in one query and associates them in memory.
3. What is the difference between load() and loadMissing()?
load() always loads. loadMissing() only loads if not already loaded.
4. How do you count relations efficiently?
Use withCount() to add a count subquery without loading models.
5. Challenge: Optimize a query with constrained eager loading.
<?php
$articles = Article::with(["category", "comments" => function ($q) {
$q->where("approved", true)->latest()->limit(5);
}])->withCount(["comments", "likes"])->latest()->paginate(20);
FAQ
Mini Project: Optimized Post Listing
Build an optimized post listing with proper eager loading.
<?php
class PostController {
public function index(): View {
$posts = Post::with(["author" => fn($q) => $q->select("id", "name", "avatar")])
->withCount(["comments", "likes"])
->with(["tags:name,slug"])
->published()
->latest()
->paginate(20);
return view("posts.index", compact("posts"));
}
}
What's Next
Eloquent Performance Eloquent Serialization Eloquent Query Scopes
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro