AfriBa Labs Logo
AfriBa LabsProduct Studio
Ruby on Rails
12 min read•October 1, 2026

Rails N+1 Queries Explained: Elimination, Preload, and Strict Loading

How accidental query loops degrade database throughput and the exact ActiveRecord techniques to eliminate them.

AB
AfriBa Labs Engineering
Backend & Systems Engineering
Ruby on RailsActiveRecordPostgreSQLDatabase OptimizationPerformance
Key Takeaways & Core Principles
An N+1 query occurs when 1 parent query is followed by N separate child queries inside an iteration loop.
'preload' forces two separate queries (parent records + child records with WHERE foreign_key IN (...)).
'eager_load' forces a single query with a LEFT OUTER JOIN across all specified tables.
'includes' delegates decision-making: it uses 'preload' by default, or 'eager_load' if references are queried in WHERE clauses.
Enable 'strict_loading' in development/test to raise ActiveRecord::StrictLoadingViolationError before bad queries hit production.

1. The Anatomy of an N+1 Query

Why a clean 3-line loop spawns hundreds of database round-trips

ActiveRecord associations are lazily loaded by default. When an application queries a list of authors and later iterates over each author's books, ActiveRecord fires a fresh SELECT query for every single iteration step.

With 100 authors, this results in 1 initial query plus 100 individual child queries: 101 round-trips over the network to PostgreSQL. Database connection pool saturation and slow response times inevitably follow.

ruby
# BAD: Causes 1 + N queries (1 for authors, 50 for books)
authors = Author.limit(50)
authors.each do |author|
  puts "#{author.name}: #{author.books.count} books"
end

# GOOD: Preloads all books in just 2 queries total
authors = Author.includes(:books).limit(50)
authors.each do |author|
  puts "#{author.name}: #{author.books.size} books" # Note: use .size, not .count
end
N+1 query problem vs Eager loading solution

2. Under the Hood: Preload vs Eager Load vs Includes

Understanding how ActiveRecord chooses between JOINs and IN (...) queries

Many developers use 'includes' without understanding whether ActiveRecord will perform a SQL JOIN or multiple separate queries. The choice carries significant memory and database performance implications.

Preload executes two clean queries using WHERE foreign_key IN (...). This is usually the fastest strategy when loading large 'has_many' collections because it avoids multiplying result set rows through Cartesian products.

Eager load forces a LEFT OUTER JOIN. This is necessary when you need to filter or sort parent records based on child columns (e.g., Author.eager_load(:books).where('books.published_year > 2020')).

ruby
# Strategy 1: Preload (2 separate SQL queries)
# Query 1: SELECT * FROM authors LIMIT 20;
# Query 2: SELECT * FROM books WHERE author_id IN (1, 2, 3...);
Author.preload(:books).limit(20)

# Strategy 2: Eager Load (Single SQL query with LEFT OUTER JOIN)
# Query 1: SELECT authors.*, books.* FROM authors
#          LEFT OUTER JOIN books ON books.author_id = authors.id;
Author.eager_load(:books).where("books.in_stock = true")
Explicitly declaring preload versus eager_load

3. Strict Loading: Permanent Prevention in Rails 6.1+

Turning accidental lazy-loading bugs into immediate test failures

Relying on code reviews or log audits to catch N+1 queries is error-prone. Modern Rails applications prevent N+1 regressions automatically using strict_loading.

When strict loading is enabled on an association or relation, any attempt to lazily load an unloaded association raises ActiveRecord::StrictLoadingViolationError in test environments.

ruby
# Enable globally on a model association
class Author < ApplicationRecord
  has_many :books, strict_loading: true
end

# Or enable per query in controller actions
def index
  @authors = Author.strict_loading.includes(:books).page(params[:page])
end

# In config/environments/development.rb and test.rb:
config.active_record.action_on_strict_loading_violation = :raise
Enabling strict_loading on models and queries

Related Developer Tools & Products

Continue Reading

All Articles →