Skip to content
Python

Python Assert: Syntax, Examples, and When to Use It

By The EbookWale Team · Updated October 6, 2026 · 19 min read

Python assert checks a condition and raises AssertionError when it is false. Use it for developer assumptions, not input or security validation.

Python assert is for internal conditions whose failure means the code is buggy; use raise for validation or behavior that must always run.

What Does Assert Do in Python?

An assertion is an executable claim about the current state of a program. It says, “If the code is working as intended, this condition must be true.”

The simplest form contains the assert keyword followed by a condition:

assert condition

When assertions are enabled, Python tests the condition using its normal truth-value rules. If the condition is truthy, execution continues. If it is falsy, Python raises AssertionError. Under -O, the assertion is omitted.

What Python does with assertassertreachedAssertionsenabled?Conditiontruthy?continueAssertionErroryestruefalseno: python -O skips it
An assertion either continues, raises an error, or is skipped under `-O`.

This standalone example shows both paths:

temperature = 18
assert temperature > 0
print("The assertion passed")

try:
    assert temperature < 0
except AssertionError:
    print("The assertion failed")
The assertion passed
The assertion failed

The first assertion does nothing visible because temperature > 0 is true. The second condition is false, so it raises AssertionError. The try block catches that exception only so the example can display a stable result.

An assert statement is tied to the built-in __debug__ constant. Its behavior is equivalent to this nested structure:

if __debug__:
    if not condition:
        raise AssertionError

That outer if __debug__ matters. Python normally sets __debug__ to True, but it sets the constant to False when started with the -O option. In that mode, assertions are removed rather than evaluated.

This is why assert should express a developer assumption, not enforce a rule that the running application depends on. It belongs alongside the deeper language behavior covered in a structured Python learning roadmap, after basic conditions and exceptions are clear.

Python Assert Syntax and Worked Examples

Python has two assertion forms:

assert condition
assert condition, message

When assertions are enabled, the second form supplies an argument to AssertionError when the condition is falsy; under -O, the assertion is omitted. A useful message states what should have been true and includes the value that violated the assumption.

Here is a complete standalone example:

def average(numbers):
    if not numbers:
        raise ValueError("numbers must not be empty")
    return sum(numbers) / len(numbers)


result = average([2, 4, 6])
assert result == 4
print(result)

try:
    assert result == 5, f"expected 5, got {result}"
except AssertionError as error:
    print(type(error).__name__)
    print(error)
4.0
AssertionError
expected 5, got 4.0

The explicit check validates the collection state: numbers must contain at least one item before the division occurs. The assertion checks a function result. The failed assertion demonstrates the optional message.

If a failed assertion is not caught, Python prints a traceback. The traceback shows the chain of calls that reached the failing line, followed by a final line such as:

AssertionError: expected 5, got 4.0

Start at that final line to read the message, then inspect the preceding file and line information to locate the failed assumption. The assertion identifies the symptom. The actual defect may be earlier in the calculation.

Assertions use normal truth-value testing. None, False, numeric zero, empty strings, and empty collections are falsy. Non-empty collections are generally truthy, even when the item inside them is itself falsy.

The container has its own truth valueA value by itself0falsyA list containing one item0truthyThe list is not empty.
Python tests whether the list is empty, not whether every item in it is truthy.
values = [None, False, 0, "", [], {}, [0]]
print([bool(value) for value in values])
[False, False, False, False, False, False, True]

That makes concise checks possible, but the expression must still communicate the intended rule:

score = 82
assert 0 <= score <= 100, f"score outside expected range: {score}"

pending_jobs = ["resize-image"]
assert pending_jobs, "scheduler selected an empty job queue"

def square(number):
    return number * number

answer = square(5)
assert answer == 25, f"unexpected square result: {answer}"

These examples cover three common shapes:

  • A value must remain inside a valid internal range.
  • A collection must have the expected state at a specific point.
  • A function must produce a promised result.

Do not use a bare assertion merely because an expression can be tested for truth. assert result says only that result must be truthy. assert result.status == "ready" records a much more precise assumption.

When to Use Assert and When to Raise an Exception

The practical decision rule is based on who or what can cause the condition to fail.

Ask where failure can come fromA condition mayfailCan a caller orexternal sourcecause it?yesinput · file · APInoonly defective coderaisemust stay activeassertdeveloper alarm
The source of failure determines whether a check is an assertion or required validation.

An internal invariant is a condition that can fail only because the program has a defect. It describes something the code promises to keep true. Assertions are suitable for checking internal invariants because a failure means a developer must fix the code.

Examples include:

  • A balance calculated from validated internal records cannot be negative.
  • A sorting function’s result must contain the same number of items as its input.
  • An internal state transition must produce one of the states handled by the next step.
  • A function’s result must satisfy its documented postcondition.

A postcondition is an invariant checked after an operation. This standalone function uses assertions to verify properties of its own result:

Postconditions guard the exitinput[5, 1, 3]operationsortresult[1, 3, 5]same itemcount?ascendingorder?returnboth pass
Postconditions inspect the completed result before the function returns it.
def ascending_copy(numbers):
    result = sorted(numbers)

    assert len(result) == len(numbers), "sorting changed the item count"
    assert all(
        result[index] <= result[index + 1]
        for index in range(len(result) - 1)
    ), f"result is not ascending: {result}"

    return result


print(ascending_copy([5, 1, 3]))
[1, 3, 5]

Both conditions describe promises made by the implementation. If either fails, the sorting code or the assumptions around it are defective.

An apparently unreachable branch can also expose a broken invariant. Suppose an internal workflow is intended to produce only "queued" or "complete". Reaching a third state indicates a programming defect. An assertion can document that assumption during development:

def internal_label(state):
    if state == "queued":
        return "Waiting"
    if state == "complete":
        return "Done"

    assert False, f"unhandled internal state: {state}"

There is an important qualification: with -O, that final assertion disappears and the function can return None. If continuing from the branch would corrupt state, disclose protected information, or otherwise break required behavior, use an explicit exception instead:

def internal_label(state):
    if state == "queued":
        return "Waiting"
    if state == "complete":
        return "Done"

    raise RuntimeError(f"unhandled internal state: {state}")

Expected runtime failures belong to raise, even when they indicate that a value is unacceptable. User input, API responses, file availability, permissions, and authentication can all fail without proving that the program contains a defect.

This validation must remain active:

def set_discount(percent):
    if not isinstance(percent, (int, float)):
        raise TypeError("percent must be a number")

    if not 0 <= percent <= 100:
        raise ValueError("percent must be between 0 and 100")

    return percent

Calling set_discount(140) supplies invalid input, so the function raises ValueError. The caller can correct the value or report the problem. Replacing either check with assert would allow invalid input through when assertions are removed.

Use the same reasoning for failures outside the program’s control:

SituationUseReason
Internal result violates a postconditionassertFailure indicates defective code
Internal data structure has an impossible stateassert, if optimized fallthrough is harmlessFailure identifies a broken developer assumption
Public function receives an invalid argumentraiseCallers can supply invalid values
Parsed external data has the wrong formatraiseExternal data is not controlled by the implementation
A required file is missingExplicit exception handlingMissing resources are runtime failures
A user lacks permissionExplicit validation or raisePermission checks must always run
Authentication failsExplicit validation or raiseSecurity behavior cannot disappear
Continuing after an impossible branch would be unsaferaiseThe failure must occur in every execution mode

This distinction also prevents a common misunderstanding: assertions are not a shorter spelling for validation. They have different execution semantics. The common Python mistakes guide covers similar cases where compact code silently changes the intended contract.

Using Assert in Pytest and Unittest

Assertions inside tests have a focused purpose: they compare observed behavior with expected behavior. The test fails when those values differ.

Pytest accepts ordinary Python assert statements. It rewrites assertions before executing the test so that failures can report values from common subexpressions.

Start with this application module:

# cart.py
def total(prices):
    if any(price < 0 for price in prices):
        raise ValueError("prices cannot be negative")

    return sum(prices)

A pytest test can use a plain assertion:

# test_cart_pytest.py
from cart import total


def test_total_adds_prices():
    assert total([3, 4, 5]) == 12

If the comparison fails, pytest can display useful details about the values involved. This richer test report does not change the purpose of assert in application code. The ValueError inside total remains necessary because rejecting negative prices is part of the function’s behavior.

The equivalent unittest test uses a named TestCase method:

# test_cart_unittest.py
import unittest

from cart import total


class TestTotal(unittest.TestCase):
    def test_total_adds_prices(self):
        self.assertEqual(total([3, 4, 5]), 12)


if __name__ == "__main__":
    unittest.main()

assertEqual(actual, expected) communicates an equality check directly. Unittest also provides assertTrue for truth checks and assertRaises for expected exceptions.

For example, this unittest verifies the validation already implemented by total:

def test_total_rejects_negative_prices(self):
    with self.assertRaises(ValueError):
        total([3, -1, 5])

The matching pytest style uses its exception-checking helper:

import pytest

from cart import total


def test_total_rejects_negative_prices():
    with pytest.raises(ValueError):
        total([3, -1, 5])

The roles are separate:

MechanismTypical locationPurpose
Plain assert under pytestTest codeCompare actual behavior with expected behavior
TestCase.assertEqualUnittest codeCheck equality with a named assertion method
TestCase.assertTrueUnittest codeCheck that an expression is truthy
assertRaises or pytest.raisesTest codeVerify that expected code raises an exception
Explicit raiseApplication codeEnforce validation and required runtime behavior

A test assertion verifies that application validation works. It does not perform that validation for the application.

Testing and validation have different homesAPPLICATION BOUNDARYinvalid prices?raise ValueErrorreturn sumTEST CODEpytest assertunittest methodCALLERSweb appscriptAPIcallsobservesall entervalid
Tests check the guard; the application’s explicit exception is the guard.

Assertions, exception questions, and failure interpretation also appear in Python assessment tests and broader Python interview questions. The useful skill is explaining why a particular check belongs in a test, an internal assertion, or an explicit exception.

What Happens to Assert Under Python -O?

Python’s -O command-line option removes assert statements and code conditional on __debug__. Under ordinary execution, __debug__ is True. Under python -O, it is False.

The following harmless program makes the difference visible. The assertion condition calls a function that increments a counter, so the output proves whether Python evaluated the condition.

# optimized_assert.py
checks_run = 0


def check_inventory():
    global checks_run
    checks_run += 1
    return False


try:
    assert check_inventory(), "inventory mismatch"
except AssertionError as error:
    print(type(error).__name__, str(error))

print("__debug__:", __debug__)
print("checks run:", checks_run)

Run it normally:

python optimized_assert.py
AssertionError inventory mismatch
__debug__: True
checks run: 1

The condition runs, returns False, and causes AssertionError. The exception message is evaluated and displayed.

Now run the same file with optimization enabled:

python -O optimized_assert.py
__debug__: False
checks run: 0

The assertion disappears. Python does not call check_inventory(), does not increment the counter, and does not evaluate the message.

The same source code, two execution trackspython optimized_assert.pypython -O optimized_assert.pycheck_inventory()checks_run = 1AssertionErrorrunsassert ...removedchecks_run = 0skips the whole assertno call · no message · no error
With `-O`, Python jumps over every part of the assertion.

This example establishes two rules.

First, never put a required side effect inside an assertion condition:

# Wrong: registration can disappear under python -O.
assert register_user(user)

Perform the action separately, then handle its result with ordinary control flow:

registered = register_user(user)

if not registered:
    raise RuntimeError("user registration failed")

Second, do not put required work inside the assertion message. The optional message expression is needed only when the assertion fails, and the whole assertion can be removed:

# Wrong if record_failure() is required application behavior.
assert balance >= 0, record_failure(balance)

Authorization and authentication checks are especially clear cases. This code is unsafe because optimized execution can skip the check:

# Wrong: access control must not depend on assert.
assert user.is_admin, "administrator access required"
delete_record(record_id)

Use an explicit branch and exception:

if not user.is_admin:
    raise PermissionError("administrator access required")

delete_record(record_id)

The same rule applies to input validation, state updates, billing decisions, data integrity checks, and any other behavior that must remain active. Assertions may describe internal assumptions around such code, but they cannot enforce its required contract.

Common Assert Mistakes

The most deceptive syntax mistake is writing assert(condition, message):

condition = False
message = "condition should be true"

assert (condition, message)
print("The tuple was truthy")

Standard output:

The tuple was truthy

This does not supply two parts to the assert statement. (condition, message) creates one non-empty tuple, and a non-empty tuple is truthy. Python emits a SyntaxWarning for this pattern, but the correct syntax is still worth memorizing:

assert condition, message

Other mistakes follow from treating assertions as ordinary error handling:

  • Putting required side effects in the condition or message.
  • Catching AssertionError as routine control flow.
  • Using assert to reject user input or external data.
  • Protecting permissions, authentication, or sensitive operations with assert.
  • Writing vague messages such as "failed" instead of naming the broken assumption.
  • Packing several unrelated checks into one complicated Boolean expression.
  • Assuming a pytest failure report makes assert appropriate for production validation.

Use this checklist when choosing:

QuestionChoice
Can this fail only because the program is defective?Consider assert
Can a user, caller, file, network response, or external service cause it?Use validation and raise
Must the check run in every execution mode?Use explicit control flow
Does the expression perform required work?Move the work outside the assertion
Would continuing after failure be unsafe?Raise an explicit exception
Is this a test comparison?Use the testing framework’s normal assertion style

Where the Books Fit

Assertions make more sense once conditions, exceptions, functions, testing, and application boundaries are studied together. Python in Three Months follows that job-ready path and gives each tool a defined role, so an internal debugging check does not accidentally become missing production validation. Keep the decision rule close: assert developer assumptions, and explicitly raise the failures the application must enforce.

Frequently asked questions

What does assert do in Python?

The assert statement checks a condition that should be true if the program is correct. When assertions are enabled, a falsy condition raises AssertionError, optionally with a message supplied after a comma; under -O, the assertion is omitted.

When should I use assert instead of raise?

Use assert for internal assumptions whose failure indicates a programming defect. Use raise for invalid arguments, external data, missing resources, permissions, authentication, and any behavior that must remain active in every execution mode.

Are Python assert statements removed with the -O option?

Yes. When Python starts with the -O option, assert statements and code conditional on __debug__ are removed, and __debug__ is false. Required validation, authorization, state changes, and other application behavior must therefore use explicit code.

Can I add a message to a Python assertion?

Yes. Write assert condition, message without wrapping both expressions in one pair of parentheses. If the assertion fails, Python passes the message to AssertionError.

Should pytest tests use plain assert statements?

Pytest accepts standard Python assert statements and can show values from common subexpressions when a test fails. Unittest instead provides named methods such as assertEqual, assertTrue, and assertRaises.