Laravel Aug 12, 2026 · 6 min read

Eloquent beyond the basics: eager loading done right

If you've ever watched Laravel's debug bar show 500 queries on a single page load, you've met the N+1 problem. Here's how to fix it — and when you should leave it alone.

Eloquent beyond the basics: eager loading done right cover

Eloquent makes relationships feel effortless — which is exactly why the N+1 problem sneaks up on you. You write one line of code, and Eloquent quietly fires a query for every row you're looping over. On a list page with 50 users, that's 51 queries. On a busy dashboard, it's a firehose.

The N+1 problem, explained

Consider this seemingly innocent snippet. For each user in the collection, Eloquent runs a fresh posts query the first time you touch it:

UserController.php
// 1 user query + 50 post queries = 51 queries total
$users = User::all();
foreach ($users as $user) {
    echo $user->posts->count();
}

Rule of thumb: if you see the debug bar showing 1 + n queries on a collection loop, that's your signal to eager load.

Fix it with with()

Eager loading tells Eloquent to fetch the related models upfront, in a single query, using a WHERE IN (...). Two queries instead of fifty-one:

UserController.php
$users = User::with('posts')->get();
// 2 queries: users + posts WHERE user_id IN (...)

Nested and conditional eager loading

Relationships are chainable, and you can constrain them with closures when you only need a subset:

UserController.php
// nested two levels deep
User::with(['posts.author', 'posts.tags'])->get();

// constrained: only published posts
User::with([
    'posts' => fn ($q) => $q->where('published', true),
])->get();

When not to eager load

  • Tiny result sets. For under ~20 rows the extra query joins aren't worth the complexity.
  • Counts only. Use withCount('posts') instead of loading whole models.
  • Already cached. If the relationship is served from cache, eager loading only adds latency.

Pro tips

Use load() for lazy, on-demand eager loading when the relationship is only needed conditionally. Reach for withCount() to avoid hydrating models you never render. And keep the debug bar open — it's the fastest way to catch an N+1 before it reaches production.

TL;DR

Every loop iteration that queries a relationship is a sign. Reach for with(), constrain with closures, count with withCount(), and keep an eye on your query count.

That's it — two queries instead of fifty-one. Your debug bar (and your database) will thank you.

Laravel Eloquent Performance
Share:
FR

Fazale Rabbi

Laravel Full-Stack Developer writing about what I build and debug.

Let's work together →

Keep reading