Eager Loading / Lazy Loading
Optimizing queries with Eager Loading
Eager Loading and Lazy Loading are two different methods for loading relationships in Eloquent. Choosing correctly between these two can greatly affect application performance.
Lazy Loading (Default):
Lazy Loading means relationships are only loaded when you access them. This is the default behavior of Eloquent.
N+1 Query Problem:
When you lazy load relationships and use them in a loop, an additional query is executed for each parent record. This causes N+1 query problem:
- 1 query for parent records
- N queries for relationships (one query per parent)
Eager Loading (Solution):
Eager Loading means relationships are preloaded. Using with(), all relationships are loaded in one or a few queries.
Benefits of Eager Loading:
- <strong>Performance</strong>: Reducing queries from N+1 to 2-3 queries
- <strong>Efficiency</strong>: Better use of database resources
- <strong>Scalability</strong>: Better for large datasets
- <strong>Memory</strong>: Better memory usage
Use Cases:
- Relationships used in loops
- Relationships displayed in views
- API responses that include relationships
- Reports and analytics queries
Examples
Lazy Loading (N+1 Problem)
<?php
// Lazy Loading - N+1 Problem
$users = User::all(); // 1 query
foreach ($users as $user) {
echo $user->posts->count(); // N queries (one per user)
// Total: 1 + N queries
}
// If you have 100 users, this executes 101 queries!
Lazy loading that causes N+1 query problem.
Eager Loading (Solution)
<?php
// Eager Loading - Solution
$users = User::with('posts')->get(); // 2 queries total
// Query 1: SELECT * FROM users
// Query 2: SELECT * FROM posts WHERE user_id IN (1,2,3,...)
foreach ($users as $user) {
echo $user->posts->count(); // No additional queries
// Total: 2 queries regardless of number of users
}
Eager loading that solves N+1 problem.
Multiple Relationships
<?php
// Eager load multiple relationships
$users = User::with(['posts', 'profile', 'roles'])->get();
// 4 queries total:
// 1. Users
// 2. Posts
// 3. Profiles
// 4. Roles
// Nested eager loading
$users = User::with('posts.comments')->get();
// Loads users, their posts, and comments on those posts
// Multiple nested
$users = User::with([
'posts.comments',
'posts.tags',
'profile'
])->get();
Eager loading multiple relationships and nested relationships.
Eager Loading with Constraints
<?php
// Eager load with constraints
$users = User::with(['posts' => function ($query) {
$query->where('published', true)
->orderBy('created_at', 'desc');
}])->get();
// Eager load with count
$users = User::withCount('posts')->get();
// Adds posts_count attribute
// Eager load with exists
$users = User::withExists('posts')->get();
// Adds posts_exists attribute
// Eager load with min/max/avg/sum
$users = User::withAvg('posts', 'views')->get();
// Adds posts_avg_views attribute
Eager loading with constraints and aggregate functions.
Lazy Eager Loading
<?php
// Load relationships after model is retrieved
$users = User::all();
// Later, eager load relationships
$users->load('posts');
// Or load for specific models
$users->loadMissing('posts');
// Conditional lazy eager loading
if ($needsPosts) {
$users->load('posts');
}
Lazy eager loading that loads relationships after model is retrieved.
Preventing Lazy Loading
<?php
// Prevent lazy loading in development
// In AppServiceProvider
Model::preventLazyLoading(! app()->isProduction());
// This will throw exception if lazy loading is attempted
$user = User::find(1);
$posts = $user->posts; // Throws exception in development
// Must use eager loading
$user = User::with('posts')->find(1);
$posts = $user->posts; // OK
Preventing lazy loading in development to catch N+1 problems.
Eager Loading Performance Tips
<?php
// Select specific columns
$users = User::with('posts:id,user_id,title')->get();
// Eager load with select
$users = User::with(['posts' => function ($query) {
$query->select('id', 'user_id', 'title', 'created_at');
}])->get();
// Use chunking with eager loading
User::with('posts')->chunk(100, function ($users) {
foreach ($users as $user) {
// Process user with preloaded posts
}
});
Performance tips for eager loading.
Use Cases
- Preventing N+1 query problem
- Optimizing queries with relationships
- Improving performance in loops
- API responses with relationships
- Reports and analytics queries
- Views that display relationships
Common Mistakes
- Forgetting eager loading in loops
- Using lazy loading for relationships used in views
- Eager loading relationships that aren't used
- Forgetting nested relationships
- Using eager loading for single records
- Eager loading with large datasets without chunking
Best Practices
- Always use eager loading for relationships in loops
- Use with() for relationships used in views
- Use nested eager loading for deep relationships
- Use withCount() for counting relationships
- Use select() for selecting specific columns
- Use preventLazyLoading() in development
- Apply eager loading only for relationships that are used
Edge Cases
- Eager loading with very large datasets
- Nested eager loading with multiple levels
- Eager loading with polymorphic relationships
- Eager loading in queue jobs
- Lazy loading with conditional access
- Eager loading with complex constraints
Performance Notes
- Eager loading can solve N+1 problem
- Use select() for selecting specific columns
- Use withCount() instead of loading relationships for counts
- Use chunking for large datasets
- Eager loading overhead is very low
- Use indexes for foreign keys
Security Notes
- Ensure eager loaded relationships are authorized
- Use constraints for filtering sensitive relationships
- Ensure eager loading doesn't expose sensitive data
Interview Points
- What is N+1 query problem and how is it solved?
- What is the difference between eager loading and lazy loading?
- How can you eager load multiple relationships?
- What is withCount() and when is it used?
- How can you prevent lazy loading?
- When should you use eager loading?
Version Notes
- Laravel 11.x: Improved performance in eager loading
- Laravel 11.x: Better support for nested eager loading
- Laravel 10.x: Improved preventLazyLoading()
- Laravel 9.x: Improved withCount() performance