How to make faster queries using Django ORM

If you’re building Django applications at any decent scale, you’ll quickly realize:
Slow queries → slower apps → frustrated users.
The good news?
Django’s ORM is extremely powerful — if you know how to use it right.
In this article, we’ll dive into how to write better, faster Django queries using some simple but highly effective patterns.
We’ll cover:
Why
.only()
mattersWhen and how to use
prefetch_related
(similarly select_related)How to avoid unnecessary data loading
Practical example based on real-world code
Let’s go 🚀.
The Problem: Unoptimized Django ORM Queries
Imagine you have a Django app where users are connected to RSS feed URLs.
Each user can have multiple feeds, but you only want to cache a list of active feed IDs for each user.
Here’s what a basic implementation might look like:
active_users = user.objects.filter(is_active=True).prefetch_related('user_feeds')
for user in active_users:
feed_ids = [feed.feed_id for feed in user.user_feeds if feed.is_active]
# cache feed_ids for each user somewhere
Looks fine, right?
Not really.
This query is loading all fields from both User
and FeedURL
models — even if you only need user_id
and feed_id
!
It executes something like this:
SELECT * FROM user
WHERE is_active = true;
SELECT * FROM feedurl
INNER JOIN user_feedurl
ON feedurl.id = user_feedurl.feedurl_id;
This loads unnecessary fields which results in
Bigger queries sent to the DB
More network traffic
More RAM usage in your app
Slower iteration over results
Optimisations:
- Use
.only()
to Fetch Only What You Need
Always restrict the fields Django fetches using .only()
. You never wanna pull all the columns in memory until required.
User.objects.filter(is_active=True).only("user_id")
This will tell Django:
“Only load the
user_id
field. Ignore everything else."
✅ Faster DB query
✅ Smaller objects in memory
✅ Safer against accidentally touching heavy fields later
2. Prefetch Related Data Smartly
When dealing with related models (e.g., user feeds), use prefetch_related
— but also control what gets prefetched.
Instead of blindly prefetching all fields:
active_feed_qs = FeedURL.objects.filter(is_active=True).only("feed_id")
active_users = User.objects.filter(is_active=True).only("user_id").prefetch_related(
Prefetch("user_feeds", queryset=active_feed_qs, to_attr="active_user_feeds")
)
✅ only("feed_id")
ensures we don't load unnecessary RSS feed fields
✅ to_attr="active_user_feeds"
gives a custom attribute that avoids touching the real user_feeds
relation again
Now when looping:
for user in active_users:
feed_ids = [feed.feed_id for feed in user.active_user_feeds]
# cache it for each user as required
No extra queries are triggered.
Everything is already loaded and ready. 🏎️
3. Flatten Early, Cache Later
Whenever possible, flatten ORM objects into simple Python lists early — before any heavy processing.
active_feed_ids = [feed.feed_id for feed in user.active_user_feeds]
Then use user.active_feed_ids
everywhere instead of touching ORM objects again.
✅ Pure Python list — no ORM overhead
✅ Super fast lookups
✅ Cleaner and safer code
If you don’t need models, for flattening you can also use.values
or .values_list
while querying
User.objects.filter(is_active=True).values_list("user_id", Flat=True)
This will return a list of values instead of the model object.
User.objects.filter(is_active=True).values("user_id", Flat=True)
This will return dictionary data instead of the model object.
Here’s how an optimized function could look:
def refresh_user_rss_feed_cache():
active_feed_qs = RSSFeedURL.objects.filter(is_active=True).only("feed_id")
active_users = User.objects.filter(is_active=True).only("user_id").prefetch_related(
Prefetch("user_feeds", queryset=active_feed_qs, to_attr="active_user_feeds")
)
for user in active_users:
active_feed_ids = [feed.feed_id for feed in user.active_user_feeds]
set_cache_value(USER_RSS_FEEDS, active_feed_ids, user.user_id)
✅ Only loads necessary fields
✅ Prefetches minimal related data
✅ No extra queries inside the loop
✅ Clean and efficient
Quick Django ORM Tips 🧠
Use
.only()
: whenever you're sure you need just a few fields.Use
.defer()
: if you want to skip loading some heavy fields likedescription
orprofile_pic
.Use
select_related
: forForeignKey
relationships where you immediately need related data.Use
prefetch_related
: forManyToMany
and reverseForeignKey
relationships.Flatten early: turn queryset objects into plain Python structures if you just need IDs, names, etc.
Conclusion
Good Django ORM usage is not about being fancy.
It’s about being intentional:
👉 Fetch only what you need.
👉 Prefetch efficiently.
👉 Avoid surprises by flattening early.
These small habits will make your Django apps faster, cheaper to run, and more reliable.
Apart from these use indexing and caching for efficiency.
Happy coding! ⚡
Subscribe to my newsletter
Read articles from Prasang Maheshwari directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
