Lazy loading, by default
Eloquent loads relationships on demand. Accessing $user->posts fires a query at the moment of access:
$users = User::take(100)->get(); // 1 query
foreach ($users as $user) {
echo $user->posts->count(); // 1 query per user — 100 total
}
// Total: 1 + 100 = 101 queries → N+1Eager loading with with()
$users = User::with('posts')->take(100)->get(); // 2 queries total
foreach ($users as $user) {
echo $user->posts->count(); // no query fires — already loaded
}
// Total: 2 queries, regardless of user count
Under the hood: one SELECT * FROM users LIMIT 100, then one SELECT * FROM posts WHERE user_id IN (1, 2, 3, ..., 100). Eloquent wires the posts back to their users in-memory.
Nested relationships
// Load users + their posts + each post's comments + each comment's author
User::with('posts.comments.author')->get();
// 4 queries total:
// 1. SELECT * FROM users
// 2. SELECT * FROM posts WHERE user_id IN (...)
// 3. SELECT * FROM comments WHERE post_id IN (...)
// 4. SELECT * FROM users WHERE id IN (...) -- comment authorsFour queries for any number of rows. Beats N+1-squared-squared-squared, which would be thousands.
Constraining eager loads
// Only eager-load published posts
User::with(['posts' => fn ($q) => $q->where('published', true)])->get();
// Only specific columns (reduces memory)
User::with('posts:id,user_id,title')->get();
// Load counts without the rows themselves
User::withCount('posts')->get();preventLazyLoading — catch N+1 in dev
app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
In non-production, any lazy-loaded relationship throws LazyLoadingViolationException. You're forced to either eager-load or explicitly $user->loadMissing('posts'). Catches N+1 as a bug during development rather than as a slow page in production.
Don't enable in production — a missed eager-load becomes a 500 for real users instead of a slow page.
When lazy loading is actually fine
On a single model, not a collection: $user = User::find(1); $user->posts; fires one query. No N+1. The problem only materializes with loops over collections.