This innocent-looking Laravel loop runs 101 database queries. It's called the N+1 problem, and it's the most common reason Laravel apps get slow. I measured everything below with Eloquent on a real database, re-checked on Laravel 13.

Video version:

The innocent loop

$posts = Post::all(); // 1 query foreach ($posts as $post) { echo $post->user->name; // +1 query PER post! } // 100 posts = 101 queries 

Post::all() is one query. But each time you touch $post->user, Laravel lazy loads the author with another query. That's one query for the posts, plus one per post: N+1.

What the database actually sees

Turn on the query log (DB::enableQueryLog()) or install Laravel Debugbar:

select * from "posts" select * from "users" where "users"."id" = 1 limit 1 select * from "users" where "users"."id" = 2 limit 1 select * from "users" where "users"."id" = 3 limit 1 -- … 97 more … 

On a real server, every one of those is a network round trip to the database.

The fix: eager load with with()

$posts = Post::with('user')->get(); foreach ($posts as $post) { echo $post->user->name; // no extra query } 

The loop doesn't change at all. Laravel loads the posts, then loads every author in one query, and matches them up in memory:

select * from "posts" select * from "users" where "users"."id" in (1, 2, 3, …, 100) 

That's 2 queries instead of 101, and it stays at 2 whether you have 10 posts or 10,000.

More eager-loading tricks

// several relations at once (3 queries) Post::with(['user', 'comments'])->get(); // nested: users → posts → comments (3 queries) User::with('posts.comments')->get(); // only need a number? (1 query) Post::withCount('comments')->get(); // $post->comments_count 

withCount() adds a comments_count attribute using a subquery, so you don't load a single comment model just to count them.

Make Laravel catch N+1 for you

In AppServiceProvider::boot():

use Illuminate\Database\Eloquent\Model; public function boot(): void { Model::preventLazyLoading(! app()->isProduction()); } 

Now any lazy load during development throws:

Illuminate\Database\LazyLoadingViolationException: Attempted to lazy load [user] on model [Post] but lazy loading is disabled. 

It even tells you exactly which relation to eager load. The ! app()->isProduction() part keeps production from crashing over a missed eager load.

Cheat sheet

Relation inside a loop? N+1 risk
Spot it query log or Laravel Debugbar
Fix it Post::with('user'): 2 queries, not 101
More relations with(['a', 'b']), with('posts.comments')
Only need a count withCount()
Catch it automatically Model::preventLazyLoading() in dev

How many queries does your slowest page run? Check it. 👀

I post one tested coding tip every day on YouTube at Refactorly. The 30-second version of this one is here.