Eloquent N+1 Query Optimization in Laravel | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Eloquent Query Optimization: Killing N+1 Problems at the Source        On this page       1. [  The N+1 Problem Is Still Killing Your App ](#the-n1-problem-is-still-killing-your-app)
2. [  Detecting N+1 Queries in Development ](#detecting-n1-queries-in-development)
3. [  The Obvious Fix: Eager Loading ](#the-obvious-fix-eager-loading)
4. [  Nested Eager Loading ](#nested-eager-loading)
5. [  Subtle N+1 Patterns That Survive Review ](#subtle-n1-patterns-that-survive-review)
6. [  Lazy Loading Inside Blade ](#lazy-loading-inside-blade)
7. [  Counting Relations ](#counting-relations)
8. [  Polymorphic Relations ](#polymorphic-relations)
9. [  Architectural Prevention ](#architectural-prevention)
10. [  Key Takeaways ](#key-takeaways)

  ![Eloquent Query Optimization: Killing N+1 Problems at the Source](https://cdn.msaied.com/475/39c5bf23764003ebc9ac32d840752d49.png)

  #laravel   #eloquent   #performance   #database   #optimization  

 Eloquent Query Optimization: Killing N+1 Problems at the Source 
=================================================================

     27 Jul 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

  10 sections  

1. [  01   The N+1 Problem Is Still Killing Your App  ](#the-n1-problem-is-still-killing-your-app)
2. [  02   Detecting N+1 Queries in Development  ](#detecting-n1-queries-in-development)
3. [  03   The Obvious Fix: Eager Loading  ](#the-obvious-fix-eager-loading)
4. [  04   Nested Eager Loading  ](#nested-eager-loading)
5. [  05   Subtle N+1 Patterns That Survive Review  ](#subtle-n1-patterns-that-survive-review)
6. [  06   Lazy Loading Inside Blade  ](#lazy-loading-inside-blade)
7. [  07   Counting Relations  ](#counting-relations)
8. [  08   Polymorphic Relations  ](#polymorphic-relations)
9. [  09   Architectural Prevention  ](#architectural-prevention)
10. [  10   Key Takeaways  ](#key-takeaways)

       The N+1 Problem Is Still Killing Your App
-----------------------------------------

Every Laravel developer knows what an N+1 query is. Far fewer have a systematic strategy for preventing them at the architectural level. This article goes beyond the basics — we'll look at detection tooling, subtle N+1 patterns that survive code review, and structural approaches that make the problem harder to introduce in the first place.

Detecting N+1 Queries in Development
------------------------------------

The fastest feedback loop is `DB::listen` or a dedicated package. In a service provider or test bootstrap:

```php
DB::listen(function (QueryExecuted $event) {
    if (str_contains($event->sql, 'select')) {
        logger()->debug($event->sql, ['time' => $event->time]);
    }
});

```

For a more structured approach, [Laravel Debugbar](https://github.com/barryvdh/laravel-debugbar) groups duplicate queries visually. In CI, use `spatie/laravel-query-detector` to fail tests when N+1 queries are detected automatically.

```php
// In a Pest test
use Spatie\QueryDetector\QueryDetector;

it('loads posts without N+1', function () {
    app(QueryDetector::class)->enable();

    $posts = Post::with('author', 'tags')->get();
    $posts->each(fn ($p) => $p->author->name);

    expect(app(QueryDetector::class)->getDetectedQueries())->toBeEmpty();
});

```

The Obvious Fix: Eager Loading
------------------------------

You already know `with()`. The subtlety is *when* to apply it.

```php
// Fragile: caller must remember to eager-load
$posts = Post::all();

// Better: scope it at the model level for common access patterns
class Post extends Model
{
    protected $with = ['author']; // always eager-load author
}

```

Use `$with` sparingly — it runs on every query including ones that don't need the relation. Prefer explicit `with()` calls in repository or query builder methods.

### Nested Eager Loading

```php
$posts = Post::with([
    'comments.author',
    'tags',
    'media' => fn ($q) => $q->where('type', 'cover'),
])->get();

```

Constrained eager loads let you filter the relation without triggering extra queries.

Subtle N+1 Patterns That Survive Review
---------------------------------------

### Lazy Loading Inside Blade

```blade
{{-- This fires a query per post --}}
@foreach ($posts as $post)
    {{ $post->author->name }}
@endforeach

```

Enable `Model::preventLazyLoading()` in `AppServiceProvider` for non-production environments:

```php
Model::preventLazyLoading(! app()->isProduction());

```

This throws a `LazyLoadingViolationException` immediately, surfacing the problem during development.

### Counting Relations

```php
// N+1: fires a COUNT query per post
$posts->each(fn ($p) => $p->comments->count());

// Correct: one extra query total
$posts = Post::withCount('comments')->get();
// Access via $post->comments_count

```

### Polymorphic Relations

Polymorphic `morphTo` relations are notorious. Use `morphWith` to eager-load the correct related types:

```php
$activities = Activity::with('subject')->get();
// subject could be Post, Comment, or Video — each type fires its own query

// Better: constrain per type
$activities = Activity::with([
    'subject' => fn ($morph) => $morph
        ->morphWith([
            Post::class => ['author'],
            Comment::class => ['post'],
        ]),
])->get();

```

Architectural Prevention
------------------------

The most durable fix is structural. If your query logic lives in a dedicated class, you control the eager loads in one place:

```php
final class PostListQuery
{
    public function handle(int $perPage = 20): LengthAwarePaginator
    {
        return Post::query()
            ->with(['author', 'tags', 'media'])
            ->withCount('comments')
            ->latest()
            ->paginate($perPage);
    }
}

```

Controllers and Filament resources call `PostListQuery` — they never build raw queries. Eager loading is a concern of the query object, not the caller.

Key Takeaways
-------------

- Enable `Model::preventLazyLoading()` in development; it catches violations immediately.
- Use `withCount()` instead of loading full relation collections just to count them.
- Constrain eager loads with closures to avoid over-fetching.
- Centralise query construction in dedicated query classes or repositories to enforce consistent eager loading.
- Add a Pest assertion using `spatie/laravel-query-detector` to your critical read paths in CI.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Feloquent-query-optimization-killing-n1-problems-at-the-source&text=Eloquent+Query+Optimization%3A+Killing+N%2B1+Problems+at+the+Source) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Feloquent-query-optimization-killing-n1-problems-at-the-source) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  When should I use `$with` on a model versus calling `with()` explicitly?        Use `$with` only for relations that are genuinely required on every access path for that model. For most cases, prefer explicit `with()` calls in query objects or repository methods so you don't over-fetch on queries that don't need the relation. 

      Q02  Does `Model::preventLazyLoading()` affect production performance?        No. The recommended pattern is `Model::preventLazyLoading(! app()-&gt;isProduction())`, which only activates the guard in non-production environments. In production it is a no-op with zero overhead. 

      Q03  How do I handle N+1 issues in Filament table resources?        Override the `getEloquentQuery()` method on your Filament resource's ListRecords page or the Resource class itself to add `with()` and `withCount()` calls. Filament does not automatically eager-load relations referenced in columns. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Stable Contracts](https://cdn.msaied.com/476/2fe247e6705cd8624395e9194ff80703.png) laravel api eloquent 

### Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Stable Contracts

Go beyond basic transformers. Learn how to implement sparse fieldsets, conditionally load relationships, and e...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 27 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-api-resources-sparse-fieldsets-conditional-relationships-and-stable-contracts) [ ![Laravel API Rate-Limiting: Custom Limiters, Per-Route Strategies, and Header Contracts](https://cdn.msaied.com/474/47b5edd1bda5625b01ae20f7684ed199.png) laravel api rate-limiting 

### Laravel API Rate-Limiting: Custom Limiters, Per-Route Strategies, and Header Contracts

Go beyond the default throttle middleware. Build named rate limiters with dynamic keys, per-user tiers, and co...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 27 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-api-rate-limiting-custom-limiters-per-route-strategies-and-header-contracts-1) [ ![Profiling Laravel with Blackfire and Xdebug: Finding Real Bottlenecks](https://cdn.msaied.com/473/5b2261450fb7c3c68870997e338c2e1b.png) laravel performance profiling 

### Profiling Laravel with Blackfire and Xdebug: Finding Real Bottlenecks

Stop guessing where your Laravel app is slow. Learn how to use Blackfire and Xdebug profilers together to pinp...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 26 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/profiling-laravel-with-blackfire-and-xdebug-finding-real-bottlenecks-1) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
