← All posts

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

Fixing a slow Laravel page: the N+1 query problem, cover

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_count

Same 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 status or created_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.