Django
Query Filtering
Programming
Web Development
Python

How can I filter a Django query with a list of values?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Django, the standard way to filter by a list of values is the __in lookup. It translates naturally into an SQL IN clause and lets you express "match any of these values" without writing repetitive OR conditions. The main things to get right are empty-list behavior, field type compatibility, and the size of the list you send to the database.

Use __in for a Single Field

The most direct pattern looks like this.

python
1from books.models import Book
2
3wanted_genres = ["Fantasy", "Science Fiction", "Mystery"]
4qs = Book.objects.filter(genre__in=wanted_genres)
5
6for book in qs:
7    print(book.title)

Django builds one query whose condition is equivalent to "genre is in this list." This is almost always the correct starting point.

It Also Works for Primary Keys and Foreign Keys

Filtering by IDs is especially common.

python
user_ids = [3, 8, 11]
users = User.objects.filter(id__in=user_ids)

The same pattern works for foreign key fields.

python
author_ids = [1, 2, 5]
books = Book.objects.filter(author_id__in=author_ids)

Using the underlying author_id field is often convenient when you already have integer IDs.

Empty Lists Need Intentional Handling

A subtle issue is what should happen when the input list is empty.

python
values = []
qs = Book.objects.filter(id__in=values)
print(qs.count())

Django treats this as a query that matches nothing, which is usually sensible. The important point is to be aware of it. If an empty list should mean "do not filter," then your application logic has to handle that before building the queryset.

python
1values = []
2qs = Book.objects.all()
3if values:
4    qs = qs.filter(id__in=values)

That difference is business logic, not an ORM trick.

Use QuerySets or Iterables as Input

The right-hand side of __in does not have to be a literal Python list. It can be any iterable, including another queryset.

python
active_author_ids = Author.objects.filter(active=True).values_list("id", flat=True)
books = Book.objects.filter(author_id__in=active_author_ids)

This is useful when one query naturally feeds another.

Do Not Replace __in With Manual Q Chains Unnecessarily

Beginners sometimes write many Q expressions joined with | for a simple same-field membership test.

python
1from django.db.models import Q
2
3qs = Book.objects.filter(
4    Q(genre="Fantasy") | Q(genre="Science Fiction") | Q(genre="Mystery")
5)

This works, but it is longer and harder to maintain than genre__in=[...]. Save Q objects for cases where different fields or mixed logical conditions are actually needed.

Large Lists Can Become a Database Concern

__in is clean, but a huge list can still be expensive for the database. If you are sending thousands of values, think about:

  • whether the field is indexed
  • whether chunking is necessary
  • whether a join or subquery would express the problem better

For most application-sized lists, __in is fine. The issue only appears when the list grows large enough to change query planning or parameter limits.

Ordering Is Not Preserved Automatically

A queryset filtered with __in does not come back in the same order as the list you passed.

python
ids = [11, 3, 8]
users = User.objects.filter(id__in=ids)

The database chooses its own result order unless you explicitly add order_by(...). If caller order matters, you need extra logic.

Combine With Other Filters Naturally

__in composes well with the rest of the ORM.

python
1recent_books = Book.objects.filter(
2    genre__in=["Fantasy", "Mystery"],
3    published=True,
4).order_by("title")

This is one reason Django's query API stays readable even as conditions get richer.

Common Pitfalls

  • Rewriting a simple membership filter with a long chain of Q objects.
  • Forgetting that an empty list with __in matches nothing.
  • Assuming the result order will match the order of input values.
  • Using __in on a huge unindexed field and then blaming Django for slow SQL.
  • Passing values whose types do not match the field being filtered.

Summary

  • Use field__in=values when you want to match any value from a list.
  • The pattern works for normal fields, primary keys, and foreign keys.
  • Empty input lists usually produce an empty result set.
  • '__in is simpler than manual Q chaining for single-field membership tests.'
  • For very large lists, think about indexing, chunking, or better query shapes.

Course illustration
Course illustration

All Rights Reserved.