Milind Daraniya

Laravel N+1 Queries: The Small Code Problem That Can Slow Down a Large Application

Published September 8th, 2026 12 min read

One of the most common Laravel performance problems I have seen is the N+1 query problem.

The interesting thing is that the code usually looks completely normal.

The application works.

The page loads.

The result is correct.

But when the number of records increases, the same code can become very slow.

This is especially important in ERP, CRM, SaaS and other applications where a page can display hundreds or thousands of records.

What is the N+1 problem?

Let's take a simple example.

Suppose we have users and their companies.

We fetch 100 users:

$users = User::get();

Then inside the Blade file:

@foreach ($users as $user)
    {{ $user->company->name }}
@endforeach

At first, this looks completely fine.

But Laravel may execute:

1 query → get users

100 queries → get each user's company

So the application can execute:

101 queries

for only 100 users.

This is the N+1 problem.

Why is it called N+1?

The first query gets the main records.

Then another query is executed for each record.

For example:

1 + 100 = 101 queries

If there are 1,000 users:

1 + 1,000 = 1,001 queries

The problem becomes much bigger as the dataset grows.

The simple Laravel solution

Laravel Eloquent provides eager loading.

Instead of:

$users = User::get();

we can use:

$users = User::with('company')->get();

Now Laravel can load the required company records together instead of querying the database separately for every user.

The important difference is not just the number of lines of code.

It can make a large difference to database load.

This problem is easy to miss

One reason N+1 queries are dangerous is that they may not appear during development.

Imagine your local database has only:

20 users

The page may load very quickly.

After the application goes live:

20,000 users

Now the same code can create a large number of database queries.

This is why I always think about query behaviour, not only whether the code works.

Check your relationships

For example:

class Order extends Model
{
    public function customer()
    {
        return $this->belongsTo(Customer::class);
    }
}

If we display customer information for many orders:

$orders = Order::get();

and later:

foreach ($orders as $order) {
    echo $order->customer->name;
}

we should consider eager loading:

$orders = Order::with('customer')->get();

The same concept applies to:

User → Company
Order → Customer
Order → Items
Product → Category
Invoice → Customer
Employee → Department
Post → Author

Any relationship accessed repeatedly can potentially create this problem.

Nested relationships can create more queries

It is not only one relationship.

For example:

Order::with('customer.company')->get();

can be useful when the page needs both:

Order
  ↓
Customer
  ↓
Company

Instead of loading relationships later inside loops.

For larger applications, I prefer deciding what data is actually required before writing the query.

Don't use eager loading blindly

There is another side to this.

Eager loading everything is not automatically better.

For example:

User::with([
    'company',
    'roles',
    'permissions',
    'orders',
    'addresses',
    'activities',
    'notifications'
])->get();

This may load far more data than the page actually needs.

So the goal is not:

Always use with().

The goal is:

Load the data that the current operation actually needs.

This is especially important for large datasets.

Select only required columns

Suppose we only need:

User ID
User name
Company name

There is no reason to load every column from a large table.

For example:

$users = User::select([
    'id',
    'company_id',
    'name'
])->with([
    'company:id,name'
])->get();

This can reduce the amount of data transferred from MySQL to PHP.

For large applications, small improvements like this can matter.

Pagination is also important

Another mistake is loading thousands of records at once.

For example:

$users = User::with('company')->get();

If there are 500,000 users, this is not a good approach for a normal web page.

Use pagination when the UI only needs a page of results:

$users = User::with('company')->paginate(50);

Now the application only needs to work with a limited number of records for that request.

Don't fix N+1 only in Blade

Sometimes developers notice that Blade is causing extra queries and try to fix the Blade code.

I prefer solving the data requirement at the query level.

Instead of:

$orders = Order::get();

and then accessing relationships repeatedly in the view, prepare the required data before passing it to the view.

For example:

$orders = Order::with('customer')
    ->latest()
    ->paginate(50);

Then the view can simply display the already-loaded relationship.

This makes the data flow easier to understand.

API responses can have the same problem

N+1 is not only a Blade problem.

It can also happen in APIs.

For example:

return UserResource::collection(
    User::get()
);

If the resource accesses:

$this->company->name

without the relationship being loaded, the API can generate many additional queries.

This is especially important when an API is used by a mobile application.

The API may return the correct response, but the backend can be doing a huge amount of unnecessary database work.

Use query logs while developing

When I am checking a performance issue, I don't only look at the PHP code.

I want to know what SQL is actually being executed.

Laravel provides tools that can help identify queries during development.

For example, query logging can show whether a loop is unexpectedly generating hundreds of queries.

Database monitoring tools can also help when debugging a real production issue.

The important thing is to measure instead of guessing.

Indexes are another part of the problem

Sometimes developers try to solve every performance problem with Eloquent changes.

But the database itself also needs to be designed properly.

For example, if we frequently query:

Order::where('company_id', $companyId)->get();

then the database index on company_id can be important.

The same applies to columns commonly used for:

WHERE
JOIN
ORDER BY
GROUP BY

The correct indexes depend on the actual queries and data distribution.

Adding indexes everywhere is not the answer either.

Indexes also have storage and write-performance costs.

Large SaaS applications need extra care

For a multi-tenant SaaS application, database queries can become much more important.

Imagine:

Company A → 10,000 orders
Company B → 500,000 orders
Company C → 2,000,000 orders

A query that works perfectly for Company A may become slow for Company C.

This is why I prefer testing queries against realistic data volumes.

Don't only test with 100 records if the production system may eventually contain millions.

My basic checklist for Laravel queries

When working on a large Laravel application, I normally check:

✓ Is there an N+1 query?
✓ Are relationships eager loaded?
✓ Are we loading unnecessary relationships?
✓ Are we selecting unnecessary columns?
✓ Is pagination required?
✓ Are the WHERE columns indexed?
✓ Are JOIN conditions indexed?
✓ Is sorting expensive?
✓ Is the dataset realistic?
✓ How many SQL queries are executed?
✓ How much data is returned?

These checks can catch many performance problems before they become production problems.

Final thoughts

N+1 queries are a good example of how a few lines of perfectly valid Laravel code can create a serious performance problem later.

The code may work with 20 records.

It may still work with 500 records.

But when the application grows to hundreds of thousands or millions of records, database behaviour becomes much more important.

For me, good Laravel development is not only about writing clean Eloquent code.

It is also about understanding what that code is doing to MySQL.

Always look beyond:

"Does it work?"

and also ask:

"How many queries does it generate?"
"How much data does it load?"
"What happens when the table becomes much larger?"

That mindset becomes especially important when building large ERP, CRM and SaaS applications.