---
title: "RR002 – Prevent N+1 with includes/eager_load"
impact: high
impactDescription: "Unaddressed N+1 queries grow linearly with page size; a 100-row page fires 101 queries instead of 2."
tags: [ruby, rails, performance, database]
---

# RR002 – Prevent N+1 with includes / eager_load

## Rule

Always eager-load associations when iterating over a collection. Use `includes`, `eager_load`, or `preload` based on whether a WHERE on the association is needed.

## Why

Accessing `post.author` inside an `.each` loop fires one SELECT per record — the classic N+1. Rails provides declarative eager loading to collapse N+1 into one extra query.

## Wrong

```ruby
# N+1: fires 1 + N queries for authors
@posts = Post.all
# view iterates: @posts.each { |p| p.author.name }

# N+1: fires 1 + N queries for comment counts
@posts = Post.all
# view: post.comments.count inside loop
```

## Correct

```ruby
# Two queries total: one for posts, one for authors
@posts = Post.includes(:author).all

# Three queries: posts + authors + tags
@posts = Post.includes(:author, :tags).page(params[:page])

# Need to filter by association column → use eager_load (LEFT JOIN)
@posts = Post.eager_load(:author).where(authors: { verified: true })

# Avoid count N+1 with counter_cache or withCount equivalent
@posts = Post.includes(:comments)
# or add counter_cache: true to the belongs_to and use post.comments_count
```

## Notes

- `includes` automatically selects between `preload` (separate queries) and `eager_load` (LEFT JOIN). Prefer `includes` unless you explicitly need one over the other.
- Use the `bullet` gem in development to surface N+1 automatically.
- Nested associations: `Post.includes(comments: :author)`.
