Why does `assert (total == 0, 'total must be zero')` never fail in Python?
answer
- Look at the syntax, not the values
- assert is a statement, not a call
- What is a tuple's truth value?
- A non-empty tuple is always truthy
- The compiler warns about the parentheses
basics
~20 sassert is a statement, so those parentheses build a two-element tuple rather than passing two arguments. A non-empty tuple is always truthy, so the condition holds for every value of total and the assertion can never fire.
solid answer
~40 sThe statement takes a condition and an optional message separated by a comma, with no argument list of its own. Putting parentheses around both means the statement receives a **single** expression — the tuple `(False, 'total must be zero')` — and no message at all. Truthiness is tested on the container, not on its first element, and a two-element tuple is always truthy, so the assertion silently passes forever. CPython's compiler does flag it at compile time with `SyntaxWarning: assertion is always true, perhaps remove parentheses?`, but a warning on standard error is easy to lose, especially once the bytecode cache is warm. The fix is `assert total == 0, 'total must be zero'`; to wrap a long line, parenthesise the condition alone or the message alone, never the comma that separates them.
code
python · 9 linestotal = 5
assert (total == 0, 'total must be zero')
print('the parenthesised assert never fires')
try:
assert total == 0, 'total must be zero'
except AssertionError as exc:
print('correct form raises:', exc)go deeper
Recall that assert is a statement with two comma-separated operands and no argument list, and that any non-empty tuple counts as true. Those two facts alone explain the whole bug.
Explain it mechanically: the parentheses form a tuple display, truthiness is tested on the container, so the condition is constant. Then show the corrected line and both safe line-wrapping forms.
Talk about detection rather than the bug: CPython's SyntaxWarning, warnings-as-errors in the test run, a linter rule, and why review and coverage both fail to catch an assertion that can never fire.
Frame it as a class of silent-guard defect alongside assertions compiled out by an optimize flag, and set the house policy that makes both impossible: tooling that fails the build, not a convention people are asked to remember.
### What the parentheses actually build `assert` is a statement, so it never takes an argument list. When you write ```python assert (total == 0, 'total must be zero') ``` the parentheses are not a call; they are a tuple display. The statement receives a **single** expression — the two-element tuple `(False, 'total must be zero')` — and no message operand at all. A tuple is truthy when it is non-empty, and this one always has two elements, so the condition is `True` on every run, for every value of `total`. The assertion can never fail. Worse, it looks like the most carefully written assertion in the file, because it has a message. The subtlety is that the truth value of the tuple has nothing to do with the truth value of its first element. `bool(())` is `False`; `bool((False,))` is `True`; `bool((False, 'anything'))` is `True`. The container is what gets tested. ### The compiler does warn CPython's compiler recognises the shape and emits a warning at compile time: ``` SyntaxWarning: assertion is always true, perhaps remove parentheses? ``` That is a `SyntaxWarning`, not a `SyntaxError`, so the module still imports and the program still runs. Two practical consequences: the warning goes to standard error at *compile* time, which for an imported module means the first time it is compiled — once the bytecode cache is warm you may never see it again; and a warning filter that hides warnings, or a service whose standard error nobody reads, hides it completely. Treating warnings as errors during a test run, or running any linter that carries this rule, catches it deterministically where the eye does not. The warning only fires for a literal tuple written inline. Assert a name that happens to hold a non-empty tuple and the compiler has nothing to go on, so it stays silent. ### Not every parenthesis is wrong `assert (total == 0)` is fine — that is a redundant grouping around one expression, not a tuple, and it behaves identically to the unparenthesised form. The bug appears the moment a comma joins the message inside the same parentheses, which is exactly what muscle memory from a language where assertions are function calls produces. For a long condition or a long message, wrap without creating a tuple: ```python assert ( total == 0 ), 'total must be zero' assert total == 0, ( 'total must be zero, got a mismatch between the parsed rows ' 'and the recomputed sum' ) ``` The first parenthesises only the condition; the second parenthesises only the message, where adjacent string literals concatenate. Either is safe; what is unsafe is one pair of parentheses containing the comma that separates condition from message. ### The neighbouring mistake The other way people try to attach a message is ```python assert total == 0 and 'total must be zero' ``` which does not raise a `SyntaxWarning` and is not always true — but it has no message either: on failure you get a bare `AssertionError`, and when the condition holds, the `and` expression evaluates to a truthy string, which is merely confusing. ### Why the bug survives so long This defect is invisible in every way a team normally notices things. Tests pass, because the assertion never fails. Code review reads it as correct English. Coverage shows the line executed. There is no exception, no log line, no latency change. It is discovered only when someone finally breaks the invariant on purpose and nothing happens, or when a reader looks hard at the syntax. It also compounds with the `-O` behaviour of assertions: a team may believe a whole family of checks is guarding them when one subset was compiled out by an optimize flag and another subset was never capable of firing in the first place. Both failures are silent, and neither is visible in the diff that introduced it. ### How to answer it in the room State the mechanism, not the rule of thumb: `assert` is a statement, the parentheses form a tuple, a non-empty tuple is truthy, so the assertion is a no-op that still costs a truth test. Then show the correct form and mention that CPython emits a `SyntaxWarning` for it, and that a linter or warnings-as-errors in the test run is what keeps it out of the codebase.
- Is `assert (total == 0)` with a single pair of parentheses also broken?No. That is a redundant grouping around one expression, not a tuple display, and it behaves identically to the unparenthesised form. The bug appears only when a comma joins the message inside the same parentheses, turning two operands into one tuple. That is also why the mistake is so easy to make: the harmless form looks like the broken one.
- How would you stop this defect from reaching the codebase in the first place?Rely on tooling rather than eyes. CPython already emits a `SyntaxWarning`, so run the test suite with warnings turned into errors and it fails deterministically. Any general-purpose linter carries an equivalent rule. Code review is the weak defence here: the broken line reads as correct English and the tests pass, because an assertion that never fires never fails.
- Why does this bug survive so long in a codebase that has tests and coverage?Because every signal a team normally watches stays green. The assertion never raises, so tests pass; the line executes, so coverage counts it; there is no log line and no latency change. It is found only when someone deliberately breaks the invariant and notices that nothing happens, or when a reader looks hard at the syntax.
It is like handing the guard a sealed envelope with your ID inside instead of showing the ID: something was presented, the ritual looks complete, and you are waved through without anyone checking what is in it.
saying these in an interview costs you the question
- Says assert is a function so parentheses are harmless
- Thinks the tuple's truth value comes from its first element
- Claims Python raises SyntaxError for the parenthesised form
- Believes passing tests prove the assertion works
- Writes assert cond and 'message' to attach a message
- Cannot show a safe way to wrap a long assertion