Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
LaravelPHPJune 20, 20264 min read

Eliminating N+1 queries in Eloquent: A Pragmatic Approach

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

LaravelPHPEloquentDatabasePerformanceOptimizationBackend
Explore the airy and geometric space of a modern library with intricate staircases and minimalist design.

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.

Understanding the N+1 Problem

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.

Pragmatic Tactics for Eliminating N+1 Queries

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.

The Standard Fix: 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.

When with() Isn't Enough

Sometimes 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.

Identifying N+1s Before They Hit Production

Detailed image of a drink can lid showing printed expiration date.

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:

PHP
public 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.

A Note on Over-Optimization

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.

Frequently Asked Questions

Close-up of a magnifying glass focusing on the phrase 'Frequently Asked Questions'.

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.

Back to Blog

Similar Posts

Explore the classic architecture of London's Lyceum Theatre with striking column details.
LaravelPHPJune 20, 20264 min read

Laravel Eloquent Performance: Mastering PostgreSQL Generated Columns

Optimize Laravel Eloquent performance by leveraging PostgreSQL generated columns. Learn to move complex transformations to the DB for faster read models.

Read more
An empty stadium with red, white, and blue seats arranged in rows, offering a patriotic theme.
LaravelPHPJune 20, 20264 min read

Laravel Event-Driven Architecture: The Transactional Outbox Pattern

Laravel Event-Driven Architecture relies on consistency. Learn how to implement the Transactional Outbox pattern to prevent data loss in distributed systems.

Read more
Close-up of a computer screen displaying an authentication failed message.
LaravelPHPJune 20, 20263 min read

Reliable background jobs: mastering Laravel queues, retries, and idempotency

Reliable background jobs are essential for production Laravel apps. Learn to implement idempotent logic and custom backoff strategies to prevent data corruption.

Read more