The N+1 query problem is one of the most common performance bugs in applications using an ORM — invisible in development with a handful of test records, and genuinely crippling once real data volume arrives.
# The N+1 problem: 1 query for orders, then N more queries, one per order's customer
orders = Order.objects.all() # 1 query
for order in orders:
print(order.customer.name) # a SEPARATE query for EACH order's customer!
# Total: 1 + N queries, where N = number of orders
# Fixed with eager loading: 2 queries total, regardless of how many orders exist
orders = Order.objects.select_related('customer') # Django ORM example
for order in orders:
print(order.customer.name) # no additional query - already loaded
Why This Is So Easy to Miss
With 10 test records in development, N+1 means “11 queries instead of 2” — invisible in a page load that already feels instant either way. With 10,000 real production records, it’s the difference between 2 queries and 10,001, turning a page load from milliseconds into seconds or worse. This exact gap between dev and production data volume is why N+1 bugs so routinely slip through code review and testing.
Catching It
Most ORMs offer a query-count assertion for tests (Django’s assertNumQueries, Rails’ assert_queries) specifically to catch N+1 regressions automatically — genuinely worth adding to tests for any endpoint returning a list with related data, since it catches this class of bug before it ever reaches production.