Fixing a slow Laravel page: the N+1 query problem
A page that lists fifty items should not run fifty-one database queries. How to spot the N+1 problem in Laravel and fix it with a few small changes.
Written byMuwafaq Murad

One of the most common reasons a Laravel page gets slow is not the server or the database. It is a loop that quietly runs a query on every pass. This is called the N+1 problem, and it is easy to fix once you can see it.
What it looks like
Say you list orders with the customer name:
$orders = Order::latest()->take(50)->get();
foreach ($orders as $order) {
echo $order->customer->name;
}That is one query for the orders, then one more query for each customer: 51 queries. With 10 rows nobody notices. With 500 rows on a busy page, it adds up fast.
The fix: eager loading
Tell Eloquent which relations you will need, and it loads them all in one extra query:
$orders = Order::with('customer')->latest()->take(50)->get();Now it is two queries, no matter how many rows. You can load nested relations too, like with('customer', 'items.product').
Counting without loading
If you only need a number, such as how many items each order has, do not load the whole relation. Use withCount:
$orders = Order::withCount('items')->get();
// $order->items_countSame idea for totals with withSum.
Make Laravel tell you
The best trick is to stop N+1 from sneaking in at all. In AppServiceProvider:
Model::preventLazyLoading(! app()->isProduction());Now, in development, Laravel throws an exception whenever a relation is loaded lazily. You find the problem while building the page, not after a client complains. It stays off in production so a missed case slows the page down but does not break it.
See the queries
To check a page, I use Laravel Debugbar or Telescope locally. Both show every query a page runs. If the same query appears dozens of times with only the ID changing, that is an N+1.
Other quick wins while you are there
Select only what you need. A list page rarely needs the long description column.
Add indexes on columns you filter or sort by, like
statusorcreated_at.Paginate. Loading every row and showing twenty is still loading every row.
These fixes take minutes, and on older projects they are often the difference between a page that feels broken and one that feels instant.