Eloquent N+1 Query Optimization in Laravel | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com) - [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [About](https://www.msaied.com#about)

           [  Contact](https://www.msaied.com#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com)
- [ProjectsCase studies](https://www.msaied.com/projects)
- [ArticlesEngineering notes](https://www.msaied.com/articles)
- [CertificatesCredentials](https://www.msaied.com/certificates)
- [AboutHow I work](https://www.msaied.com#about)
- [ContactGet in touch](https://www.msaied.com#contact)

  [Start a conversation](https://www.msaied.com#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com)
2. /
3. [Articles](https://www.msaied.com/articles)
4. /
5. Eloquent Query Optimization: Defeating N+1 Problems at the Source

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

 N+1 queries silently destroy Laravel app performance. This guide shows you how to detect, fix, and prevent them using eager loading strategies, query counting in tests, and Eloquent relationship tuning.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com#person) Published 18 Oct 2026 · Updated 18 Oct 2026 · 3 min read

ShareCopy linkCopied

 ![Eloquent Query Optimization: Defeating N+1 Problems at the Source](https://cdn.msaied.com/795/9d9dd20c259ecc6356a91021a872695c.png) 

  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 in Development](#detecting-n1-in-development)
3. [Query Counting in Pest Tests](#query-counting-in-pest-tests)
4. [Eager Loading: with(), load(), and loadMissing()](#eager-loading-codewithcode-codeloadcode-and-codeloadmissingcode)
5. [Nested Eager Loading Pitfalls](#nested-eager-loading-pitfalls)
6. [Aggregates Without Loading Collections](#aggregates-without-loading-collections)
7. [Takeaways](#takeaways)

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

You've seen it before: a page that runs 200 queries when it should run 3. The N+1 problem is one of the most common and costly performance issues in Laravel applications, and it's deceptively easy to introduce through innocent-looking Eloquent code.

This article goes beyond the basics. We'll cover detection strategies, the nuances of `with()` vs `load()`, nested eager loading pitfalls, and how to make N+1 violations fail your test suite before they reach production.

---

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

Laravel ships with a built-in guard you should enable in every non-production environment:

```php
// AppServiceProvider::boot()
use Illuminate\Database\Eloquent\Model;

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

```

This throws an `Illuminate\Database\LazyLoadingViolationException` the moment a lazy-loaded relationship is accessed, surfacing the problem immediately rather than silently degrading performance.

For a more surgical view, use `DB::listen()` or Telescope's query watcher. But the best tool for CI is query counting directly in your tests.

---

Query Counting in Pest Tests
----------------------------

```php
use Illuminate\Support\Facades\DB;

it('loads the order list without N+1 queries', function () {
    Order::factory(20)->has(Customer::factory())->create();

    $queryCount = 0;
    DB::listen(fn () => $queryCount++);

    $response = $this->getJson('/api/orders');

    $response->assertOk();
    expect($queryCount)->toBeLessThanOrEqual(3);
});

```

This pattern catches regressions the moment someone adds a new relationship access inside a loop. It's cheap to write and invaluable in a PR review.

---

Eager Loading: `with()`, `load()`, and `loadMissing()`
------------------------------------------------------

The difference matters depending on context:

- **`with()`** — eager loads at query time. Use this in controllers and repositories.
- **`load()`** — eager loads on an already-retrieved collection. Useful when you conditionally need a relationship.
- **`loadMissing()`** — only loads if the relationship isn't already loaded. Ideal in shared code paths.

```php
// Controller: always eager load
$orders = Order::with(['customer', 'items.product'])->paginate(50);

// Conditional loading after the fact
if ($includeShipments) {
    $orders->load('shipments');
}

// Safe in shared helpers — won't double-query
$orders->loadMissing('customer');

```

### Nested Eager Loading Pitfalls

Nested `with()` chains are powerful but can generate unexpected queries if you're not careful:

```php
// This loads products for ALL items across ALL orders — potentially huge
$orders = Order::with('items.product.category')->get();

// Constrain nested loads to reduce data
$orders = Order::with([
    'items' => fn ($q) => $q->select('id', 'order_id', 'product_id', 'quantity'),
    'items.product' => fn ($q) => $q->select('id', 'name', 'sku'),
])->paginate(50);

```

Always `select()` the minimum columns in constrained eager loads. Eloquent needs the foreign key columns to stitch relationships together — omitting them silently breaks the association.

---

Aggregates Without Loading Collections
--------------------------------------

A common N+1 variant involves loading a relationship just to count it:

```php
// Bad: loads all comments into memory just to count them
$posts->each(fn ($p) => $p->comments->count());

// Good: withCount() adds a comments_count attribute via a subquery
$posts = Post::withCount('comments')->get();

// Also available: withSum, withAvg, withMin, withMax
$posts = Post::withSum('items', 'quantity')->get();

```

These aggregate methods translate to efficient SQL subqueries and avoid loading any related models.

---

Takeaways
---------

- Enable `Model::preventLazyLoading()` in non-production environments — make violations impossible to miss.
- Write query-count assertions in Pest to catch N+1 regressions in CI before they ship.
- Prefer `with()` at query time; use `loadMissing()` in shared helpers to avoid double-querying.
- Always `select()` specific columns in constrained eager loads, and always include the foreign key.
- Replace `$relation->count()` calls with `withCount()` to avoid loading entire collections for aggregates.

- [laravel](https://www.msaied.com/articles?search=laravel)
- [eloquent](https://www.msaied.com/articles?search=eloquent)
- [performance](https://www.msaied.com/articles?search=performance)
- [database](https://www.msaied.com/articles?search=database)

 Frequently asked questions 
---------------------------

  Does `preventLazyLoading()` affect production performance?No. The guard is typically enabled only when `!app()-&gt;isProduction()`, so it adds zero overhead in production. It throws an exception during development and testing whenever a lazy-loaded relationship is accessed, surfacing N+1 issues immediately.

   Why does my constrained eager load return empty relationships?You likely omitted the foreign key column in your `select()` constraint. Eloquent uses the foreign key to match related models back to their parents. For example, when constraining `items`, always include `order\_id` alongside any other columns you select.

   When should I use `load()` instead of `with()`?`with()` is preferred when you know upfront which relationships you need — it runs a single eager-load query alongside the main query. Use `load()` when you retrieve a collection first and only conditionally need a relationship, or when working inside a service that receives an already-hydrated model.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

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

[About](https://www.msaied.com#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleFilament v4 Custom Table Columns: Building Reusable, Typed Column Components](https://www.msaied.com/articles/filament-v4-custom-table-columns-building-reusable-typed-column-components)  

   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 in Development](#detecting-n1-in-development)
3. [Query Counting in Pest Tests](#query-counting-in-pest-tests)
4. [Eager Loading: with(), load(), and loadMissing()](#eager-loading-codewithcode-codeloadcode-and-codeloadmissingcode)
5. [Nested Eager Loading Pitfalls](#nested-eager-loading-pitfalls)
6. [Aggregates Without Loading Collections](#aggregates-without-loading-collections)
7. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/794/ceed213d17627e0cfd9535e4ae909775.png)  · 3 min read### Filament v4 Custom Table Columns: Building Reusable, Typed Column Components

18 Oct 2026 ](https://www.msaied.com/articles/filament-v4-custom-table-columns-building-reusable-typed-column-components) [ ![](https://cdn.msaied.com/793/9ec9d2cf138593ecc4292fa9fa49ee7a.png)  · 3 min read### Testing Filament Resources, Actions, and Form Assertions with Pest

18 Oct 2026 ](https://www.msaied.com/articles/testing-filament-resources-actions-and-form-assertions-with-pest-5) [ ![](https://cdn.msaied.com/792/bb8ca53bdeeb8f56f86b6a34e8e5a0f2.png)  · 4 min read### Domain-Driven Design in Laravel: Actions, DTOs, and Value Objects Without Bloat

18 Oct 2026 ](https://www.msaied.com/articles/domain-driven-design-in-laravel-actions-dtos-and-value-objects-without-bloat-4) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/sitemap.xml)
