DEV Community

HotfixHero
HotfixHero

Posted on

The N+1 Query Problem: When Your Code Needs a Performance Review (Not a Hug)

You shipped a feature. It worked great when you tested it with 10 records locally. Then it hit production, the data grew, and now your page load is measured in coffee breaks, not milliseconds. Your stakeholders are calling. The Product Owner is pacing. You start hearing that classic, self-soothing lie: "It works on my machine!"

No, champ, it doesn't. You've walked right into N+1 query hell, and you didn't even pack a bag.

Look, ORMs (Object-Relational Mappers) are great. They let junior devs—and let's be honest, exhausted senior devs—write complex database operations without remembering the exact syntax for a LEFT OUTER JOIN. But they also make it ridiculously easy to accidentally turn one simple data request into a thousand database trips.

The Anatomy of a Thousand Cuts

The N+1 problem happens when you need a collection of data and related details for each item.

  • You execute 1 query to fetch the list of primary items (e.g., all 1000 blog posts).
  • Then, you iterate through that list, and for each of the N items (the 1000 posts), the ORM lazily executes 1 new query to fetch the related data (e.g., the author's name).

Total queries? 1 + N. If you have 1,000 posts, that’s 1,001 database hits for one page load. Your database server is currently screaming, and the cost of fetching a few KB of data has exploded into a high-latency disaster.

The only thing lazier than the developers who write this code is the Lazy Loading feature itself.

The Ugly Code You Need to Burn

This is what I find every Tuesday morning when I look at a stack trace. This example is simplified, but the pattern is universal across every framework.

// 1. Fetch 1000 Posts (1 Query)
const posts = db.posts.findAll();

// 2. The N Loop: This is the slow-motion train wreck.
// If N=1000, the database is hit 1000 more times.
for (const post of posts) {
  // Post.author is lazily loaded.
  // The moment you access it, BOOM! A new DB call.
  const author = post.author; // <-- 1 Query per post
  console.log(`Post: ${post.title}, Author: ${author.name}`);
}

// Total Queries: 1001. That's efficiency, folks.

Enter fullscreen mode Exit fullscreen mode

The Hotfix: Eagerly Load Your Life

Your database is a workhorse, not a toy you poke repeatedly. It's built to do complex joins in milliseconds. Stop treating it like a vending machine you feed quarters to one at a time.

The fix is "Eager Loading" (or Include or Preload, depending on your ORM). You tell the ORM exactly what relationships you need, and it intelligently bundles everything into one or two highly optimized queries, usually using a single, glorious JOIN or a fast IN clause.

As a sidestep, Some more specific platforms like Servoy have features that let you do this very efficiently. Instead of having to configure complex SQL in every data retrieval call, or worrying about falling back into the lazy-loading trap when iterating a FoundSet, Servoy allows you to leverage its View Foundset functionality. This lets you declare the complex join logic once, effectively defining a single, highly performant SQL query that the platform uses to fetch the combined data set of posts and authors. The front-end immediately works off this efficient, pre-joined data, eliminating the $N+1$ problem before it even starts. It's a pragmatic, business-first approach to performance.

But back to the more direct approach:


// The Fix: Tell the ORM to JOIN the 'author' table up front.
// Total Queries: 1 (or 2, depending on the ORM's strategy).

const postsWithAuthors = db.posts.findAll({
  include: 'author' // Eagerly loads all related authors data now
});

// The loop now just accesses in-memory data. 0 new queries.
for (const post of postsWithAuthors) {
  const author = post.author; // <-- Data is already loaded. Instant access.
  console.log(`Post: ${post.title}, Author: ${author.name}`);
}

Enter fullscreen mode Exit fullscreen mode

The Business Takeaway

Why do I care? Because performance isn't a technical detail—it's a direct driver of revenue.

  • Slow pages kill conversions.
  • Excessive database load means you're scaling up hardware unnecessarily.
  • Latency costs money, user trust, and my sanity when I have to fix your mess.

If you don't care about writing clean, efficient code for the craft, at least care about it because your company is paying for every millisecond of wasted time.

Go check your logs. I guarantee there's a slow-loading endpoint in there right now, silently multiplying your server bill. Stop asking your ORM to do the work of a teenager being asked to clean their room—it only does the bare minimum unless you're explicit.


I just saved your job and your server budget. You're welcome.

Would you like me to dive deeper into the different types of eager loading (JOIN vs. Batching) next?

Top comments (0)