Eliminating N+1 queries in Eloquent is essential for Laravel performance. Learn how to identify, debug, and solve these database bottlenecks in production.

During a recent refactor of a legacy dashboard, I noticed the page load time jumped from 300ms to over 2 seconds after we added a simple user profile display. It turned out we were firing an individual database query for every single row in a table, a classic N+1 performance killer that brings even the fastest Laravel applications to their knees.
The N+1 issue happens when you loop through a collection of models and trigger a database query for each item to fetch a relationship. If you have 50 users and you're calling $user->profile inside a foreach loop, Laravel executes one query to get the users and then 50 additional queries to fetch their profiles.
It’s tempting to think your database handle is fast enough, but network latency and query overhead add up quickly. I’ve seen production systems crash under load simply because a single controller was executing 400+ queries per request.
The most effective way to address this is by using Eloquent's eager loading feature. Instead of lazy loading relationships, you tell Laravel to fetch them upfront in a single, efficient query.
with()For most use cases, the with() method is your best friend. It transforms your N+1 problem into a simple 2-query operation.
PHP#6A9955">// Bad: N+1 $users = User::all(); foreach ($users as $user) { echo $user->profile->bio; } #6A9955">// Good: Eager Loading $users = User::with('profile')->get(); foreach ($users as $user) { echo $user->profile->bio; }
By adding with('profile'), Eloquent performs the secondary query using an IN clause, fetching all related profiles in one trip to the database.
with() Isn't EnoughSometimes you need to load relationships conditionally or based on complex logic. I once had a project where we needed to load user statistics only if the user had an active subscription. Loading everything with with() was bloating memory usage by 40MB, which triggered OOM errors on our smallest containers.
In cases like this, I use load() to lazy-eager load relationships after the initial query:
PHP$users = User::where('active', true)->get(); #6A9955">// Load relationship only if a condition is met if ($needsStats) { $users->load('stats'); }
This keeps your memory footprint low while still avoiding the N+1 trap. It’s a middle ground that often proves more stable than aggressive eager loading.

You shouldn't wait for your users to complain about slow response times. The best way to catch these issues is by using Laravel's built-in query detection. In your AppServiceProvider, you can tell Laravel to throw an exception when an N+1 query is detected:
PHPpublic function boot(): void { Model::preventLazyLoading(! app()->isProduction()); }
When this is enabled in your local environment, the application will throw a LazyLoadingViolationException the moment it detects an un-eager-loaded relationship. It’s annoying at first, but it forces you to write performant code from day one.
Don't fall into the trap of eager loading everything. If you have a relationship that is only used in 5% of your requests, eager loading it every time creates unnecessary overhead. Just like when I was designing a clean service layer in Laravel without over-abstraction, the goal is to keep the code readable and the performance predictable.
If you find yourself needing to eager load across many levels of relationships, consider if your database schema could be flattened or if a custom query would be more efficient. Sometimes, a raw JOIN query is significantly faster than hydrating hundreds of Eloquent models.

How do I know if I have an N+1 issue? Use the Laravel Debugbar package. It lists every query executed during a request and highlights duplicates.
Is eager loading always faster?
Not always. If you are loading a large relationship that you only need one item from, it might be faster to use a specific query or a join. Always profile your code using DB::listen or a tool like Clockwork.
Does with() work with nested relationships?
Yes. You can use dot notation to eager load deep relationships, like User::with('profile.address')->get(). Just be careful not to over-fetch data you don't actually need.
I still occasionally get caught by N+1s when working with complex polymorphic relationships. It’s easy to overlook a hidden relationship in a view partial. My advice? Keep preventLazyLoading on in your local environment and trust the tools to tell you when you've missed something. It’s better to fix a small query issue during development than to debug a production performance spike at 3 AM.
Laravel Event-Driven Architecture relies on consistency. Learn how to implement the Transactional Outbox pattern to prevent data loss in distributed systems.
Read more