Preloading data with Rails: Includes, Preload, and Eager Load

September 14, 2025

The N+1 query problem is one of the most common performance issues when working with ORMs. Rails' includes method is the go-to solution for this.

Includes

Rails' includes method is smart. it dynamically chooses between different approaches based on our query structure.

Under the hood, includes typically delegates to preload, but this behavior can change depending on the circumstances. This intelligence makes includes the best way to solve the N+1 issues

Preload

When we use preload directly, or when includes chooses it internally, Rails executes separate queries for each association:

books = Book.preload(:authors)

This approach generates distinct queries: one for the primary records and additional queries for each association. For a book-author relationship, we would see something like:

SELECT * FROM books
SELECT * FROM authors WHERE id IN (1, 2, 3, ...)

The beauty of this approach lies in its simplicity and efficiency. Each query is straightforward, optimized, and Rails handles the data stitching in memory.

Eager Load

The eager_load method takes a different approach, combining everything into a single query using LEFT OUTER JOINs:

books = Book.eager_load(:authors)

This generates a more complex query:

SELECT books.*, authors.* 
FROM books 
LEFT OUTER JOIN authors ON authors.id = books.author_id

While this reduces database round trips, it comes with trade-offs. The query becomes more complex, potentially slower, and can consume significantly more memory with large datasets.

Joins

There's a third method that often causes confusion: joins. This method creates INNER JOINs but critically doesn't load associated records into memory.

books = Book.joins(:authors).where(authors: { published: true })

This is perfect for filtering records based on association attributes, but if we try to access the associated data later, we will trigger the N+1 problem we were trying to avoid.

When Includes Makes the Switch

Here's where includes demonstrates its intelligence. When we add conditions that reference associated tables, includes automatically switches from preload to eager_load:

Book.includes(:authors).where(authors: { name: 'Stephen King' })

Rails makes this switch because it needs the association data available in the same query to apply the WHERE condition effectively.