Killing N+1 queries at the database layer is the fastest way to stabilize your app. Learn how to identify, debug, and eliminate performance-killing bottlenecks.

I remember sitting at my desk around 2:00 AM, staring at a dashboard that was screaming red. Our primary order processing service was timing out, and the culprit wasn't a complex algorithm—it was a classic N+1 query pattern that had survived our staging environment.
We’d built a feature to list recent orders alongside customer details. In development, with ten records, it was lightning fast. In production, with a user list spanning thousands, the application was firing one initial query for the orders and then an individual SELECT for every single customer. The latency jumped from 20ms to roughly 850ms, effectively locking up our connection pool.
The N+1 problem occurs when your application performs one query to fetch a set of records, and then, for each of those records, executes an additional query to fetch related data. If you fetch 100 orders, you’re hitting the database 101 times.
Most of us learn the basics of Eloquent basics: models, relationships, and your first queries early on, but it's easy to forget that convenience often masks the underlying network round-trips. When you iterate through a collection and call a relationship property, the ORM is often lazy-loading that data in real-time.
Our first instinct was to just "add more indexes." We looked at our Indexing Strategy for App Developers: Stop Slow Queries and assumed the database was the bottleneck. We added a composite index on the customer_id and created_at columns.
It didn't help.
The database wasn't struggling to find the rows; it was struggling to process 101 separate requests. The overhead of opening and closing connections, parsing SQL, and managing transactions for each small query was the real killer. We weren't fighting an indexing issue; we were fighting an architectural one.
Instead of relying on the ORM to "magically" fetch related data, we shifted our focus to eager loading and database-level aggregation.
If you are working in a framework like Laravel, you already know about with(). However, sometimes you need more control. When Eliminating N+1 queries in Eloquent: A Pragmatic Approach, I usually recommend looking at the raw SQL generated by your queries.
If you're stuck, use this approach:
Laravel Debugbar or simple log-based query logging will show you exactly how many queries are hitting the DB.with() to pull related models in a single WHERE IN (...) query.JOIN or use a subquery to pull the data you need directly into the result set.For example, instead of fetching customers separately, use a join:
SQLSELECT orders.*, customers.name as customer_name FROM orders JOIN customers ON orders.customer_id = customers.id WHERE orders.status = 'pending' LIMIT 50;
This reduces 51 queries to exactly one. The database is optimized to handle joins; your application server is not optimized to handle the network latency of 50 extra round-trips.
Sometimes, even eager loading isn't enough. If you’re performing complex transformations on the data, consider moving that logic into the database. I’ve had success with Laravel Eloquent Performance: Mastering PostgreSQL Generated Columns to pre-calculate values that would otherwise require multiple queries to compute.
By pushing this logic into the schema, you ensure that the data is ready before the application even asks for it. It turns a compute-heavy read operation into a simple index scan.
I still see developers trying to solve N+1 issues by simply caching the result of every individual query in Redis. While caching helps, it’s a band-aid. If your cache expires, your database will still get crushed by that burst of 100+ queries.
Next time I tackle this, I’ll prioritize profiling the query count during the PR review process itself. It’s much cheaper to catch a nested loop in a controller than it is to debug it at 2:00 AM on a Tuesday. We're still refining our automated test suite to fail builds if the query count exceeds a certain threshold—a "query budget," if you will. It’s a work in progress, but it beats the alternative.
Master an indexing strategy for app developers to fix slow production queries. Learn how to read EXPLAIN plans, pick the right columns, and avoid overhead.