DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Automatically Detect and Fix N+1 Queries with Claude Code

What is the N+1 Query Problem?

The N+1 query problem is one of the most common performance issues in web applications.

Imagine you want to display a list of blog posts with each author's name:

# Bad example (Django ORM)
posts = Post.objects.all()   # 1 query
for post in posts:
    print(post.author.name)  # 1 query per post -> N queries
Enter fullscreen mode Exit fullscreen mode

If there are 100 posts, this executes 101 SQL queries. That's the N+1 problem.

Finding Candidates with grep

Before using Claude Code, scan the entire codebase for N+1 candidates.

# Detect loop-based access patterns in Django
grep -rn "for.*in.*\.objects\." src/
grep -rn "\.objects\.get\|\.objects\.filter" src/ | grep -v "select_related\|prefetch_related"
Enter fullscreen mode Exit fullscreen mode
# N+1 patterns in Express.js + Sequelize
grep -rn "findOne\|findAll" src/ | grep -v "include:"
Enter fullscreen mode Exit fullscreen mode

This gives you a list of suspicious spots. Feed them to Claude Code next.

Using Claude Code to Detect and Fix

Run the following at your project root:

claude "Detect all N+1 queries in this code and suggest fixes.
Target: src/views/posts.py
Format: line number, current code, fixed code"
Enter fullscreen mode Exit fullscreen mode

Django: select_related / prefetch_related

# Fixed (ForeignKey: select_related)
posts = Post.objects.select_related('author').all()
for post in posts:
    print(post.author.name)  # 1 query with JOIN

# Fixed (ManyToMany / reverse: prefetch_related)
posts = Post.objects.prefetch_related('tags').all()
for post in posts:
    print([tag.name for tag in post.tags.all()])  # 2 queries with IN clause
Enter fullscreen mode Exit fullscreen mode

Verify the query count with:

from django.db import connection

posts = Post.objects.all()
for post in posts:
    _ = post.author.name

print(len(connection.queries))  # Before fix: 101
Enter fullscreen mode Exit fullscreen mode

Express.js + Sequelize

// Bad
const posts = await Post.findAll();
for (const post of posts) {
  post.author = await User.findByPk(post.authorId); // N queries
}

// Fixed (Eager loading)
const posts = await Post.findAll({
  include: [{ model: User, as: 'author', attributes: ['name', 'email'] }]
});
Enter fullscreen mode Exit fullscreen mode

Rails (ActiveRecord)

# Bad
Post.all.each { |p| puts p.author.name }

# Fixed
Post.includes(:author).each { |p| puts p.author.name }
Enter fullscreen mode Exit fullscreen mode

Automate with the /code-review Skill

Checking manually every time is inefficient. With Claude Code's /code-review skill, you can automate this on every PR.

# Configure in .claude/commands/code-review.md
claude /code-review src/
Enter fullscreen mode Exit fullscreen mode

Example skill config (.claude/commands/code-review.md):

Perform a code review with the following rules.

## Checklist
1. Detect N+1 queries (DB calls inside loops)
2. WHERE clauses without indexes
3. Unused variables / imports

## Output Format
- filename:line_number
- Description of the issue
- Fixed code snippet
Enter fullscreen mode Exit fullscreen mode

This automatically detects N+1 queries before every commit.

Benchmark: Before vs After

Results from a real Django project:

Condition Query Count Response Time
Before fix (100 posts) 101 450ms
After select_related 2 28ms
Improvement -98% 16x faster

N+1 fixes are zero-cost changes that deliver the biggest performance gains — always tackle these first.

Summary

  1. Use grep to find DB calls inside loops
  2. Use Claude Code to identify issues and generate fixes
  3. Automate with the /code-review skill on every PR
  4. Django: select_related / prefetch_related; Sequelize: include:

N+1 queries are easy to miss but simple to fix. With AI, you can scan large codebases in minutes.


Code Review Pack — A 3-skill set: /code-review, /test-gen, /refactor. N+1 detection is included.

👉 note.com/myougatheaxo

Top comments (0)