← Back to DevBytes

Fix 'AssertionError' in Python

Understanding AssertionError in Python

An AssertionError is a built-in exception in Python raised when an assert statement fails. It acts as a sanity check during development, ensuring that certain conditions hold true at specific points in your program. When the condition evaluates to False, Python immediately throws an AssertionError, halting execution unless caught explicitly.

This mechanism is a powerful debugging aid, but it can also become a source of confusion when used incorrectly in production code. In this tutorial, you will learn what an AssertionError is, why it matters, how to use assertions effectively, how to fix common issues related to it, and the best practices to adopt.

What Is AssertionError?

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

At its core, the assert keyword tests an expression. The syntax is:

assert condition, optional_message

If condition is True, the program continues silently. If it’s False, Python raises AssertionError with the optional_message (if provided) as its argument.

Example:

x = 5
assert x > 0, "x must be positive"

No error occurs because 5 > 0 is True. Now consider:

x = -3
assert x > 0, "x must be positive"

This raises:

Traceback (most recent call last):
  File "example.py", line 2, in 
    assert x > 0, "x must be positive"
AssertionError: x must be positive

The error message is exactly the optional_message you provided. If you omit the message, the AssertionError appears without additional details, which can make debugging harder.

Why AssertionError Matters

Assertions are not just a way to crash a program—they serve several critical purposes:

However, AssertionError becomes a problem when it fires unexpectedly in production, or when assertions are misused as a substitute for proper error handling. Understanding how to fix it requires knowing both how to write correct assertions and how to handle cases where they fail.

How to Use assert Statements Effectively

Basic Usage and Message Clarity

Always include a descriptive message. It’s the first clue when debugging:

def apply_discount(price, discount):
    assert 0 <= discount <= 100, "Discount must be between 0 and 100 percent"
    return price * (1 - discount / 100)

Without the message, a failure would leave you staring at a bare AssertionError with no context.

Validating Function Inputs (Development Only)

Assertions are perfect for checking preconditions during development and testing:

def divide(a, b):
    assert b != 0, "Divisor must not be zero"
    return a / b

But remember: in production, you should handle such cases with explicit exceptions (like ZeroDivisionError) rather than relying on assertions, because assertions can be disabled globally.

Checking Invariants and Internal State

Use assertions to enforce that internal invariants hold:

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        self.balance -= amount
        assert self.balance >= 0, "Balance cannot go negative"

This catches logic errors in the withdraw method itself (e.g., missing checks) during development.

Asserting Type or Structure

While Python relies on duck typing, assertions can verify expected interfaces during prototyping:

def process_data(data):
    assert isinstance(data, list), "Expected a list"
    assert all(isinstance(item, int) for item in data), "All elements must be integers"
    return sum(data)

This is acceptable in internal tools or tests, but for public APIs, use explicit type checks and raise TypeError or ValueError instead.

Common Pitfalls That Cause AssertionError

1. Using Assertions for Runtime Data Validation

The biggest mistake: relying on assert to validate user input or external data in production. Assertions can be disabled with the -O (optimize) flag and the PYTHONOPTIMIZE environment variable. If someone runs your code with -O, all assert statements are skipped entirely, and your validation evaporates.

Bad example (production code):

def transfer(from_acct, to_acct, amount):
    assert amount > 0, "Amount must be positive"
    assert from_acct.balance >= amount, "Insufficient funds"
    from_acct.balance -= amount
    to_acct.balance += amount

If assertions are disabled, negative amounts or overdrafts pass silently, corrupting account balances. This is a recipe for a catastrophic bug.

Fix: Replace assertions with proper exception raising:

def transfer(from_acct, to_acct, amount):
    if amount <= 0:
        raise ValueError("Amount must be positive")
    if from_acct.balance < amount:
        raise ValueError("Insufficient funds")
    from_acct.balance -= amount
    to_acct.balance += amount

Now the validation is always active, regardless of optimization flags.

2. Catching AssertionError Broadly and Ignoring It

Occasionally, developers catch AssertionError as part of a blanket except Exception block. This silences assertion failures and hides bugs. Avoid catching AssertionError unless you have an extremely specific reason (like in unit test frameworks). Assertions are meant to crash visibly.

3. Assertions with Side Effects

Never put code with side effects inside an assert statement. If assertions are disabled, the side effect won’t occur.

Dangerous:

assert cache.update(key, value) is not None, "Cache update failed"

If optimization is on, cache.update() never runs, leading to missing data.

4. Asserting on Mutable Objects Without Considering Race Conditions

In multi-threaded or asynchronous code, an assertion that reads a mutable object may fail intermittently because another thread modified it between the assertion and the next line. Assertions are not atomic. Use proper locking and deterministic checks instead.

5. Using Assert for Expected Control Flow

Do not use assert to implement normal error handling paths. For example:

def get_setting(name):
    # Bad: relies on assertion for missing key
    settings = load_settings()
    assert name in settings, "Setting not found"
    return settings[name]

A missing key is an expected runtime condition. Use:

def get_setting(name):
    settings = load_settings()
    if name not in settings:
        raise KeyError(f"Setting '{name}' not found")
    return settings[name]

This separates "programmer mistakes" (asserts) from "expected errors" (exceptions).

How to Fix AssertionError in Your Code

Step 1: Determine If the Assertion Is a Bug Indicator or a Misuse

When you encounter an AssertionError, first ask:

Step 2: Add a Descriptive Message

If the assertion is legitimate but lacks a message, add one immediately to help future debugging:

assert len(items) == 10, f"Expected exactly 10 items, got {len(items)}"

Step 3: Convert to a Proper Exception If Needed

For conditions that must be enforced even in production, replace the assertion with a conditional raise. Choose the appropriate exception type:

Example refactoring:

# Before (assert-based)
def connect_to_db(connection_string):
    assert connection_string.startswith("postgres://"), "Invalid connection prefix"
    # ... rest of connection

# After (explicit exception)
def connect_to_db(connection_string):
    if not connection_string.startswith("postgres://"):
        raise ValueError("Connection string must start with 'postgres://'")
    # ... rest of connection

Step 4: Keep Assertions for Internal Invariants Only

Reserve assertions for conditions that reflect internal program logic and should never fail if the code is bug-free. For example:

def sort_ascending(data):
    sorted_data = sorted(data)
    # Invariant: first element <= last element (if list non-empty)
    assert not sorted_data or sorted_data[0] <= sorted_data[-1], "Sort invariant violated"
    return sorted_data

This invariant is guaranteed by sorted() – if it ever fails, something is fundamentally broken in the Python runtime or your own code, and you want to know immediately.

Step 5: Use Assertions in Tests

Unit tests naturally use assertions to validate expected outcomes. Frameworks like unittest and pytest rely on AssertionError under the hood. For example, in pytest:

def test_addition():
    assert add(2, 3) == 5, "Addition failed"

Here an AssertionError is exactly the right signal – it flags a test failure.

Step 6: Debugging Assertion Failures

When an assertion fires, use the traceback and message to identify:

For example:

def calculate_discount(price, discount_percent):
    final_price = price * (1 - discount_percent / 100)
    assert final_price >= 0, f"Negative price computed: {final_price} (price={price}, discount={discount_percent})"
    return final_price

The enhanced message immediately shows the problematic values, drastically cutting down debugging time.

Best Practices for Assertions and Avoiding AssertionError

Conclusion

AssertionError is a powerful tool for internal consistency checks during development and testing. It helps you catch bugs early, document assumptions, and maintain code correctness. However, misusing assertions as a substitute for proper runtime validation leads to fragile, dangerous code that can fail silently in production when optimizations are turned on. By understanding the difference between “this should never happen” (use assert) and “this might happen with bad input” (use raise), you can fix existing AssertionError issues and prevent new ones. Always equip your assertions with descriptive messages, keep them free of side effects, and reserve them for invariants that reveal programmer mistakes. When an assertion fires, treat it as a precious clue pointing straight at a logic flaw—fix the underlying bug, and your Python code becomes more robust and maintainable.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles