Practice · Scaling · Card 2
Count the queries this view runs
A simple index view. 50 posts. Each line shows the post title and the author name. How many SQL queries actually fire?
The code
A controller and the view it renders. Post belongs_to :user. No preloading is set up.
# app/controllers/posts_controller.rb
def index
@posts = Post.order(created_at: :desc).limit(50)
end
# app/views/posts/index.html.erb
<% @posts.each do |post| %>
<p>
<%= post.title %>
by <%= post.user.name %>
</p>
<% end %> The question
From the moment @posts is touched in the view until the view finishes rendering, how many SQL queries does Rails send to the database?
Take a moment. Pick the best answer. Wrong picks reveal why they're wrong, which is half the point.
✅ Answer breakdown
✗ A. 1 query.
Only true if posts came in with users preloaded, e.g. Post.includes(:user).order(...).limit(50). Without that, each post.user is a fresh trip to the database.
✗ B. 50 queries.
Close, but you forgot the initial query that loads the 50 posts. There's 1 query for that, plus 50 for the users.
✓ C. 51 queries.
Classic N+1: 1 query loads the 50 posts, then post.user fires another query for each of the 50 posts. Fix: Post.includes(:user).order(...).limit(50) brings it back down to 2 (one for posts, one for users).
✗ D. 100 queries.
That would be a 2N pattern, e.g. two separate per-post lookups (user and something else). Here there's only one per-post lookup.
💡 The principle
N+1 is the most common scaling bug in Rails apps, and the most invisible. The code reads naturally ("for each post, show its user"). The database sees 50 sequential SELECTs.
Fix it with includes, preload, or eager_load depending on whether you need to filter on the joined data. Catch it in development with the bullet gem or by watching the log. The cost is invisible at 50 rows in dev. It's the difference between 200ms and 5s in production.
📚 Theory
For the full walkthrough, read Scaling · Diagnose Before You Scale.