Updated 30 June 2026
If your Laravel app starts slowing down as data grows, N+1 queries in Laravel are usually the first place to look.
It’s one of those bugs that’s completely invisible until it isn’t — and by then, your app is making 200 database calls for a single page load.
Let me show you what it is, how to catch it, and how to fix it.
Say you have a list of orders and you want to show the customer name for each one.
|
1 2 3 4 5 |
$orders = Order::all(); foreach ($orders as $order) { echo $order->customer->name; } |
Looks fine, right? Here’s what actually happens:
So if you have 100 orders, that’s 101 queries. 500 orders? 501 queries.
This is the N+1 problem — 1 query to get the list, then N more queries for the related data.
Your database gets hammered, your page slows down, and nothing in the code looks obviously wrong.

Install the debugbar package:
|
1 |
composer require barryvdh/laravel-debugbar --dev |
Visit any page and you’ll see a toolbar at the bottom showing how many queries fired and how long each took.
If you see 50+ nearly identical queries, you’ve found your N+1.
Go to the Queries tab after a request. You’ll see duplicate queries stacking up — same table, slightly different IDs.
Check the official Laravel Telescope documentation for full setup instructions.
|
1 2 3 |
composer require laravel/telescope --dev php artisan telescope:install && php artisan migrate |
No packages? Drop this in your AppServiceProvider::boot() temporarily:
|
1 2 3 |
\DB::listen(function ($query) { \Log::info($query->sql, $query->bindings); }); |
Check your log file and count how many times the same query appears.
The fix is almost always eager loading.
It tells Laravel to fetch the related data upfront in one query instead of one per record.
Change this:
|
1 |
$orders = Order::all(); |
To this:
|
1 |
$orders = Order::with('customer')->get(); |
Now Laravel runs just 2 queries total — fetch all orders, then fetch all related customers in one go.
One word — with() — and you go from 501 queries to 2.
You can eager load as many relationships as you need:
|
1 |
$orders = Order::with(['customer', 'items', 'items.product'])->get(); |
The dot notation handles nested relationships.
items.product means: load each order’s items, and for each item, load its product. Still just a handful of queries total.
Sometimes you don’t know upfront what you’ll need. Use load() after the fact.
For more on the difference between the two approaches, see our guide on lazy vs. eager loading in Laravel.
|
1 2 3 4 |
$orders = Order::all(); // later in the code... $orders->load('customer'); |
Same result — just more flexible when working with collections that come from elsewhere.
If some records might already have the relationship loaded and others don’t:
|
1 |
$orders->loadMissing('customer'); |
This only fires a query for records that don’t already have the relationship loaded. Useful inside loops or conditional logic.
Select only the columns you need:
|
1 |
Order::with('customer:id,name,email')->get(); |
This loads only id, name, and email from the customers table — not the full row.
Always include the foreign key (id here) or the relationship won’t link correctly.
Cache data that rarely changes:
|
1 |
$categories = Cache::remember('categories', 3600, fn() => Category::all()); |
If the same data hits the database on every request and barely changes, cache it.
For a deeper dive into database and caching performance, check out our guide on scaling Bagisto for large product catalogs.
It covers eager loading and caching strategies at scale.
Laravel has a built-in way to throw an error whenever a lazy-loaded relationship fires unexpectedly.
Add this to AppServiceProvider::boot():
|
1 |
Model::preventLazyLoading(! app()->isProduction()); |
During development, any N+1 query throws an exception instead of silently running.
You’ll catch these before they ever reach production.

DB::listen()with() when querying, or load() after the factModel::preventLazyLoading() to catch it automatically in developmentThe fix takes 30 seconds once you know where to look. The tricky part is just noticing it exists — and now you know how.
If you have more details or questions, you can reply to the received confirmation email.
Back to Home
Be the first to comment.