Your app works great locally, but the moment you load a list of users on production, your database CPU hits 100%. The culprit is almost always a hidden performance trap known as the N+1 query problem.

  • What an N+1 query actually is under the hood
  • Why this common database pattern slows down your app
  • How to spot N+1 queries in your application logs
  • How to fix the problem using eager loading

What is an N+1 query?

An N+1 query happens when your code makes one database query to fetch a main list of items, and then makes N additional separate queries to fetch related data for each item. Imagine you have ten users, and you want to print each user's posts. Instead of asking the database for everything at once, your code asks for the users first, and then asks for posts ten separate times.

This creates eleven total database round trips just to show a simple page. As your user base grows from ten to ten thousand, your database receives tens of thousands of unnecessary requests.

The problem in code

Here is a classic example of an N+1 query written in a Node.js ORM. The code first fetches all users, and then loops through them to fetch their posts one by one.

async function getUsersWithPosts() {
  const users = await db.query('SELECT * FROM users');
  
  for (const user of users) {
    // This query runs once for EVERY single user!
    user.posts = await db.query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
  }
  
  return users;
}

If you have fifty users, this function sends fifty-one distinct queries to your database. That creates a massive bottleneck.

Render development Photo by Growtika on Unsplash

How to fix it with eager loading

The fix is to load all the related data upfront using a technique called eager loading, which combines the data into a single query using a database JOIN. Instead of asking for information piece by piece, you grab everything you need in one well-structured trip.

async function getUsersWithPostsFixed() {
  // This single query fetches users and their posts together using a JOIN
  const users = await db.query(
    'SELECT users.*, posts.* FROM users LEFT JOIN posts ON users.id = posts.user_id'
  );
  
  return users;
}

Now, no matter how many users you have, your app only makes one single query to the database.

Common mistakes to watch out for

Junior developers often assume ORMs like Prisma, Mongoose, or ActiveRecord handle everything automatically. However, hidden lazy loading inside loops can easily trigger N+1 queries without you realizing it. Always check your database logs during local development to count how many queries a single page request actually generates.

Open your local development environment today, pick one API endpoint that lists related data, and check your terminal logs to see how many SQL queries it triggers.