You ship a read endpoint. It lists some things, say devices or orders or invoices, and for each row it pulls in a bit of related data. With five rows in the demo it's instant. Six months and a few thousand rows later, the same endpoint takes seconds and sits at the top of your slow-query dashboard.

Usually the culprit isn't a slow query. It's a fast query run thousands of times. That's the N+1 problem. I recently fixed two different versions of it in the same codebase, so this post walks through both, plus the fix, plus a test that stops it coming back.

The examples use a made-up domain: a dashboard listing active devices, where each row needs the device's site, that site's latest billing window, a premium-plan flag, and an install date. The domain doesn't matter. The shapes do.

What N+1 means

You run 1 query to fetch a list of N items, then N more queries (one per item) for the related data. One plus N.