cyberangles blog

Using Multiple Conditions in Django Case When Expressions: Fixing the 'multiple values for keyword argument 'then'' TypeError

Django’s ORM (Object-Relational Mapper) is a powerful tool for writing database queries in Python, abstracting complex SQL logic into readable, maintainable code. One of its most versatile features is the Case and When expressions, which allow you to add conditional logic directly to your queries—similar to SQL’s CASE statement. However, when working with multiple conditions, developers often encounter a common error: TypeError: multiple values for keyword argument 'then'.

This blog post will demystify this error, explain why it occurs, and provide step-by-step guidance on correctly structuring Case and When expressions with multiple conditions. We’ll cover basic usage, handling compound conditions, advanced scenarios like nested logic, and best practices to avoid pitfalls. By the end, you’ll confidently use Case/When to build dynamic, conditional queries in Django.

2026-02

Table of Contents#

  1. Understanding Django’s Case and When Expressions
  2. The 'multiple values for keyword argument 'then'' Error: Why It Happens
  3. Correctly Using Multiple Conditions with Case/When
  4. Advanced Scenarios
  5. Common Pitfalls and Best Practices
  6. Conclusion
  7. References

1. Understanding Django’s Case and When Expressions#

Before diving into the error, let’s recap how Case and When work. These expressions let you define conditional logic in database queries, enabling you to return different values based on specific criteria—all executed at the database level (for better performance than filtering in Python).

Basic Syntax#

The core structure is:

from django.db.models import Case, When, Value, IntegerField  
 
queryset = MyModel.objects.annotate(  
    conditional_field=Case(  
        When(condition1, then=result1),  
        When(condition2, then=result2),  
        # ... more When clauses ...  
        default=default_result,  # Optional: fallback if no conditions match  
        output_field=IntegerField()  # Optional: explicitly define output type  
    )  
)  
  • Case: Acts as the container for conditional logic, holding one or more When clauses.
  • When: Defines a single condition and its corresponding result. It takes two key arguments:
    • A positional condition (e.g., age__gte=18).
    • A keyword argument then=... (the value to return if the condition is met).
  • default: Optional fallback value if none of the When conditions are satisfied.
  • output_field: Optional, but recommended to explicitly define the data type of the annotated field (avoids inference issues).

Simple Example#

Suppose we have a User model with an age field. We want to annotate each user with a category (e.g., "Child", "Teen", "Adult") based on their age:

from django.db.models import Case, When, Value, CharField  
 
users = User.objects.annotate(  
    category=Case(  
        When(age__lt=13, then=Value("Child")),  
        When(age__gte=13, age__lt=18, then=Value("Teen")),  # ❌ This will cause an error!  
        When(age__gte=18, then=Value("Adult")),  
        default=Value("Unknown"),  
        output_field=CharField(),  
    )  
)  

Wait a minute—this example contains a mistake that triggers the multiple values for keyword argument 'then' error. Let’s explore why.

2. The 'multiple values for keyword argument 'then'' Error: Why It Happens#

The error TypeError: multiple values for keyword argument 'then' occurs when Django misinterprets the arguments passed to a When clause. To understand why, let’s look at the signature of the When class:

class When:  
    def __init__(self, *args, **kwargs):  
        # ...  

When accepts positional arguments (for conditions) and keyword arguments (most notably then=). The critical rule is:

A single When clause can only have one condition (positional argument) and one then (keyword argument).

What Causes the Error?#

The error arises when you pass multiple positional arguments to When, which Django misinterprets as conflicting values for then. For example:

When(age__gte=13, age__lt=18, then=Value("Teen"))  # ❌ Error!  

Here, age__gte=13 and age__lt=18 are two positional arguments (they’re keyword arguments in the model filter syntax, but in When, they’re treated as positional conditions). Django sees these as two separate conditions and tries to map them to then, leading to the "multiple values for 'then'" error.

Why This Confusion Happens#

In Django filters (e.g., filter(age__gte=13, age__lt=18)), multiple keyword arguments are combined with AND logic. Developers often mistakenly apply this same syntax to When, forgetting that When expects one condition (which can be a compound condition) followed by then=....

3. Correctly Using Multiple Conditions with Case/When#

To fix the error and use multiple conditions properly, we need to structure When clauses correctly. There are two common scenarios:

Scenario 1: Multiple Independent Conditions (Different Cases)#

If you have distinct, unrelated conditions (e.g., "if X then A", "if Y then B"), pass separate When clauses to Case.

Example: Age Categories (Fixed)#

Let’s correct the earlier user categorization example. The "Teen" condition (13 ≤ age < 18) is a single compound condition, not two separate conditions. We need to wrap it in a Q object to combine the constraints with AND:

from django.db.models import Q  # Import Q for compound conditions  
 
users = User.objects.annotate(  
    category=Case(  
        When(age__lt=13, then=Value("Child")),  
        When(Q(age__gte=13) & Q(age__lt=18), then=Value("Teen")),  # ✅ Compound condition with Q  
        When(age__gte=18, then=Value("Adult")),  
        default=Value("Unknown"),  
        output_field=CharField(),  
    )  
)  
  • Q objects let you combine conditions with & (AND) or | (OR). Here, Q(age__gte=13) & Q(age__lt=18) ensures both constraints are checked.

Scenario 2: Multiple Conditions for a Single When (Compound Logic)#

Use Q objects whenever a When clause requires multiple constraints (e.g., "age ≥ 13 AND age < 18"). This avoids the "multiple values for 'then'" error by packing all conditions into a single Q object.

Example: Active Adult Users#

Suppose we want to flag users as "Active Adult" if they are ≥18 and have is_active=True. Use Q to combine the two conditions:

users = User.objects.annotate(  
    status=Case(  
        When(  
            Q(age__gte=18) & Q(is_active=True),  # ✅ AND condition  
            then=Value("Active Adult")  
        ),  
        When(  
            Q(age__gte=18) & Q(is_active=False),  
            then=Value("Inactive Adult")  
        ),  
        default=Value("Minor"),  
        output_field=CharField(),  
    )  
)  

Scenario 3: OR Conditions with Q#

For OR logic (e.g., "age < 13 OR is_staff=True"), use | with Q objects:

users = User.objects.annotate(  
    priority=Case(  
        When(  
            Q(age__lt=13) | Q(is_staff=True),  # ✅ OR condition  
            then=Value("High")  
        ),  
        default=Value("Normal"),  
        output_field=CharField(),  
    )  
)  

4. Advanced Scenarios#

Now that we’ve covered the basics, let’s explore advanced use cases for Case/When.

Nested Case/When Expressions#

You can nest Case expressions inside the then argument for complex logic (e.g., "if A then (if B then X else Y)").

Example: Tiered Pricing#

Suppose a Product model has price and category. We want to apply discounts based on category and price:

  • "Electronics": 10% off if price ≥ $100, else 5% off.
  • "Clothing": 20% off if price ≥ $50, else 10% off.
from django.db.models import F  # For field references  
 
products = Product.objects.annotate(  
    discounted_price=Case(  
        When(  
            category="Electronics",  
            then=Case(  
                When(price__gte=100, then=F("price") * 0.9),  # 10% off  
                default=F("price") * 0.95,  # 5% off  
                output_field=models.DecimalField()  
            )  
        ),  
        When(  
            category="Clothing",  
            then=Case(  
                When(price__gte=50, then=F("price") * 0.8),  # 20% off  
                default=F("price") * 0.9,  # 10% off  
                output_field=models.DecimalField()  
            )  
        ),  
        default=F("price"),  # No discount for other categories  
        output_field=models.DecimalField()  
    )  
)  
  • F("price"): References the price field of the model, allowing arithmetic directly in the query (avoids fetching data into Python first).

Using F() and ExpressionWrapper in then#

then can return not just static values (via Value()) but also dynamic values using F() (field references) or ExpressionWrapper (for type-safe arithmetic).

Example: Age Difference from Average#

Annotate users with their age difference from the average age of all users:

from django.db.models import Avg, F, ExpressionWrapper, IntegerField  
 
avg_age = User.objects.aggregate(avg=Avg("age"))["avg"]  
 
users = User.objects.annotate(  
    age_diff=Case(  
        When(  
            age__gte=avg_age,  
            then=ExpressionWrapper(F("age") - avg_age, output_field=IntegerField())  
        ),  
        default=ExpressionWrapper(avg_age - F("age"), output_field=IntegerField()),  
    )  
)  
  • ExpressionWrapper: Ensures arithmetic operations (e.g., F("age") - avg_age) are type-safe by explicitly defining output_field.

Adding a Default Value#

Always include default in Case to handle cases where no When conditions are met. Without it, the annotated field will be None, which may cause issues downstream.

# Bad: No default (returns None if no conditions match)  
Case(When(age__gte=18, then=Value("Adult")))  
 
# Good: Explicit default  
Case(  
    When(age__gte=18, then=Value("Adult")),  
    default=Value("Minor"),  
    output_field=CharField()  
)  

5. Common Pitfalls and Best Practices#

Pitfalls to Avoid#

  1. Forgetting Q for Compound Conditions
    Mistake: When(age__gte=13, age__lt=18, then=...) (triggers "multiple values for 'then'").
    Fix: Use Q(age__gte=13) & Q(age__lt=18).

  2. Misplacing then
    then is a keyword argument—never pass it as a positional argument.
    Mistake: When(age__lt=13, Value("Child")) (Django will misinterpret Value("Child") as then).
    Fix: When(age__lt=13, then=Value("Child")).

  3. Omitting output_field
    Django may infer the wrong data type (e.g., IntegerField instead of CharField). Explicitly set output_field to avoid type errors.

  4. Overcomplicating with Nested Case
    Nested Case is powerful but can reduce readability. Use it only when necessary.

Best Practices#

  • Test Queries with .query
    Print the generated SQL to debug:

    print(products.query)  # Shows the underlying SQL for the annotated queryset  
  • Use F() for Field References
    Avoid fetching data into Python to modify values (e.g., price * 0.9). Use F("price") * 0.9 to let the database handle the calculation.

  • Keep When Clauses Readable
    Split complex conditions into variables with descriptive names:

    is_teen = Q(age__gte=13) & Q(age__lt=18)  
    users = User.objects.annotate(category=Case(When(is_teen, then=Value("Teen")), ...))  

6. Conclusion#

Django’s Case and When expressions are powerful for adding conditional logic to queries, but they require careful syntax to avoid errors like multiple values for keyword argument 'then'.

Key takeaways:

  • Each When clause handles one condition (use Q objects for compound conditions like AND/OR).
  • Separate When clauses for distinct cases (e.g., "if X then A", "if Y then B").
  • Explicitly define default and output_field for robustness.
  • Use F() and nested Case for advanced calculations and complex logic.

By following these guidelines, you’ll write clean, error-free conditional queries that leverage Django’s ORM to its full potential.

7. References#