What does Python's -O flag actually do to assert statements and __debug__?
answer
- A flag whose name promises more than it does
- Something the compiler deletes, not speeds up
- One statement type never reaches bytecode
- A built-in constant flips, and its blocks vanish
- assert and __debug__ at level 1
basics
~20 sRunning the interpreter with -O compiles away every assert statement and sets the built-in constant debug to False, so if debug blocks vanish too. It performs no other optimization and does not make code faster.
solid answer
~40 s`-O` is a **compile-time** switch, not a runtime one. When the compiler builds a code object at optimization level 1, it emits no bytecode at all for `assert` statements, and it substitutes `False` for the built-in constant `__debug__`, which lets it drop any `if __debug__:` block as dead code. `sys.flags.optimize` reports the level (0, 1 or 2), and `PYTHONOPTIMIZE` sets the same thing from the environment. Despite the name it applies no compiler optimizations in the C-compiler sense: no inlining, no native code, no measurable speed-up beyond the checks it deleted. The practical consequence is that anything an `assert` expression *does* — a call with a side effect, a mutation, a log line — disappears with it, so `assert` may only carry checks the program can correctly run without.
code
console · 2 lines$ python3 -O -c "import sys; assert 1 == 2; print(__debug__, sys.flags.optimize)"
False 1go deeper
Be ready to state the two effects in one breath: assert statements are compiled out and debug becomes False. Also be ready to say plainly that the flag does not make code faster.
Explain that the removal happens at compile time, that debug is a constant the compiler inlines so if debug blocks are dropped as dead code, and that sys.flags.optimize reports the level.
Show the operational consequence: side effects hidden inside asserts change behaviour between an unoptimized CI run and an optimized production run, so the level must be identical everywhere or the flag must not be used.
Own the policy question. Decide whether the fleet runs optimized at all, given that the speed gain is nil and the blast radius includes every assert in every dependency you did not write.
`-O` is Python's oldest and most misunderstood command-line flag. The name is borrowed from C compilers, but it delivers none of what they deliver: no inlining, no loop unrolling, no native code generation. It removes two specific things from the bytecode, and it does so **when the code object is compiled**, not while it runs. ## 1. `assert` statements are not emitted Written out, `assert expr, msg` means roughly "if `__debug__` is true and `expr` is falsy, raise `AssertionError(msg)`". At optimization level 1 or higher the compiler emits *nothing* for that statement — there is no test, no jump, no exception construction in the resulting code object. This is not the same as raising and catching `AssertionError`, and it is not a runtime toggle: by the time the module is executing, the statement is simply not there. ## 2. `__debug__` becomes `False`, and its blocks are dropped `__debug__` is a compile-time constant rather than an ordinary global. The compiler inlines its value directly, so `if __debug__:` becomes `if False:` and the surrounding block is removed as unreachable code. That is why you cannot flip it yourself: ```python __debug__ = False # SyntaxError: cannot assign to __debug__ ``` The guard is useful for the expensive checks you would not want in production at all — validating a whole batch, recomputing a hash to compare it, building a debug string — because at level 1 they cost literally nothing. ## What `-O` does *not* do * It does not remove docstrings. That is level 2 (`-OO`). * It does not remove comments; comments never reach bytecode in the first place. * It does not remove annotations, type hints or any runtime type behaviour. * It does not make ordinary code faster. The only work saved is the work of the checks it deleted. Treating `-O` as a performance flag is the single most common wrong answer to this question. ## How the level is set and read back The level comes from the command line (`-O` is level 1, `-OO` is level 2) or from the `PYTHONOPTIMIZE` environment variable, which carries the level as an integer. Code reads it back through `sys.flags.optimize`, or infers level >= 1 from `__debug__` being `False`. The level applies to the whole process, including every module imported into it, and cached bytecode files are tagged with it (`opt-1`, `opt-2`) so caches built at different levels never overwrite each other. ```pycon >>> import sys >>> sys.flags.optimize, __debug__ (0, True) ``` Run the same two lines under `-O` and you get `(1, False)`. ## The part interviewers are really probing Because the statement disappears, **every side effect inside it disappears with it**. A line like `assert queue.pop() is not None` does the popping only when assertions are enabled; under `-O` the queue is never touched and the program silently takes a different path. The same trap catches `assert log_and_check(row)` and any assert whose expression mutates state. That leads to the rule: `assert` states an invariant that is true whenever the program is correct, and its job is to fail loudly during development. A condition that must hold no matter how the interpreter was started is not an assertion — it needs an ordinary `if` and a real exception, or an explicit check that runs unconditionally. One more sharp edge lives in the syntax. `assert (cond, "message")` asserts a two-element tuple, which is always truthy, so the check never fires; the compiler emits a `SyntaxWarning` for exactly this mistake. Write `assert cond, "message"` with no parentheses around the pair. ## Is it worth using? Honest answer: rarely on its own merits. The saving is the cost of checks that were usually cheap, and the risk is that production runs code you never tested. The defensible uses are a genuinely expensive `if __debug__:` guard you want compiled out, or a deployment standard that everything — CI included — runs at the same level. What you should never do is turn it on in production for speed, since there is none to gain, while the behaviour of every assert in every dependency changes underneath you.
- Does -O speed up Python the way -O2 speeds up a C compilation?No. The flag applies no classical optimizations: no inlining, no unrolling, no native code. The only work it saves is the work of the assertions and `if __debug__:` blocks it deleted, plus a slightly smaller code object. If those checks were cheap, the measurable difference is noise. Real CPython speed-ups come from the interpreter itself, and the 3.14 JIT is experimental, off by default, and enabled separately.
- Can you rebind __debug__ at runtime to re-enable assertions in one module?No. `__debug__` is a compile-time constant, so assigning to it is a `SyntaxError`, and the compiler has already inlined its value into every code object. There is no per-module or per-import override either: the optimization level is fixed for the process when the interpreter starts, and every module compiled in that process gets the same treatment.
- What breaks if an assert expression has a side effect?The side effect happens only when assertions are enabled. `assert cursor.advance()` advances the cursor in development and does nothing under `-O`, so the two runs take different paths and the optimized one is the untested path. Keep assert expressions pure — read state, compare it, return a bool — and put anything that must happen on its own statement.
It is like removing the smoke detectors before you move in: the house is a few grams lighter and behaves identically right up to the moment something is wrong.
saying these in an interview costs you the question
- Says -O applies compiler optimizations that make code faster
- Thinks -O suppresses AssertionError at runtime rather than removing the statement
- Claims you can set __debug__ = False to disable assertions
- Believes -O also strips docstrings
- Puts a side effect or real work inside an assert expression
- Writes assert (cond, "message") with the pair in parentheses