1. The N+1 Problem with Lazy Loading
By default, Eloquent uses lazy loading. It means related data is loaded only when accessed.
This often leads to the infamous N+1 query problem.
$users = User::all(); // 1 query
foreach ($users as $user) {
$user->posts; // +1 query per user ❌
}
100 users = 101 queries 😱
2. Eager Loading - the right way
The solution is eager loading:
$users = User::with('posts')->get(); // 2 queries total ✅
Nested relations:
$users = User::with('posts.comments')->get();
3. Conditional Loading
You don’t always need all relations.
- Filter related models
$users = User::with(['posts' => fn($q) => $q->where('is_active', true)])->get();
- Load only if missing
$user->loadMissing('posts');
- Combine with whereHas
$users = User::withWhereHas('posts', fn($q) => $q->where('is_active', true))->get();
4. Model::shouldBeStrict() – your dev-time guard
Enable strict mode in AppServiceProvider:
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::shouldBeStrict();
}
Benefits:
- Laravel throws exceptions on lazy loading in loops
- Catches undefined attributes
- Safer development workflow
For production you can use:
Model::preventLazyLoading(! app()->isProduction());
This way you get exceptions in dev, warnings in prod.
5. Best Practices Checklist ✅
- Always use
with()when you know you’ll need relations - Use
withCount()for aggregates instead of full relation load - For conditional loading, use
withWhereHas - Enable
Model::shouldBeStrict()during development - Monitor queries (Laravel Debugbar, Telescope, Clockwork)