1. Introduction

Django's ORM is one of its greatest strengths. It abstracts away raw SQL, lets you express database operations in clean Python, and gets you productive fast. But that convenience comes with a hidden cost: if you're not deliberate about how you fetch related objects, you'll silently generate far more queries than you intend — and you won't notice until your app slows to a crawl in production.

The most common culprit is the N+1 query problem: a pattern where fetching a list of N objects triggers an additional query for each one, resulting in N+1 total round-trips to the database. At ten rows it's invisible. At ten thousand rows, it's a disaster.

Django provides two tools to fix this: select_related and prefetch_related. This article explains how each one works internally, when to use which, and how to combine them effectively — with before/after examples and real query counts throughout.

2. Understanding the N+1 Problem