What does Python's -OO remove beyond -O, and what breaks when it does?
answer
- Level two, not level one
- Something written for humans stops existing
- An attribute on functions reads back as None
- help() and doctest go quiet
- __doc__ dropped, cached as opt-2
basics
~10 s-OO is optimization level 2: everything -O does, plus docstrings are discarded at compile time, so doc is None. Anything that reads docstrings at runtime breaks, including help(), pydoc output and doctest collection.
solid answer
~50 s`-OO` sets optimization level 2. It keeps every level-1 effect — `assert` statements and `if __debug__:` blocks compiled out — and additionally **does not store docstrings in the code objects**, so `__doc__` is `None` for modules, classes and functions across the whole process, including imported libraries. The obvious casualties are introspection tools: `help()` and `pydoc` show no description, and `doctest` finds no examples to run because the text they lived in is gone. The subtler casualties are libraries that treat `__doc__` as data — a command-line front end that builds its help text from docstrings, or a layer that parses a docstring into a schema or a route description. Only the docstring itself is dropped; other string literals, annotations and comments are unaffected (comments never reach bytecode at all). Bytecode caches for level 2 are tagged `opt-2`, so they do not collide with level-0 or level-1 caches.
code
python · 9 linessrc = 'def f():\n "the docstring"\n return 1\n'
ns2 = {}
exec(compile(src, "<demo>", "exec", optimize=2), ns2)
print(ns2["f"].__doc__)
ns0 = {}
exec(compile(src, "<demo>", "exec", optimize=0), ns0)
print(ns0["f"].__doc__)go deeper
Remember the ladder: -O is level 1 and removes assertions, -OO is level 2 and additionally throws away docstrings so doc is None. Knowing which flag does which is the whole ask here.
Explain that docstring removal happens at compile time for every module in the process, name a concrete casualty such as help() or doctest, and note that comments and annotations are unaffected.
Talk about blast radius: dependencies you did not write may read doc, and doctest silently reports zero tests rather than failing. Insist that CI runs at the same level as production.
Frame it as a cost/benefit call: a few megabytes of resident memory against running a configuration your test suite never exercised, plus an ecosystem-wide assumption that docstrings exist.
Python's optimization level is an integer, not a boolean. `-O` sets it to 1, `-OO` sets it to 2, and `PYTHONOPTIMIZE` sets it from the environment. Level 2 is a strict superset of level 1: assertions and `if __debug__:` blocks are still compiled out, and on top of that **docstrings are not stored**. ## What exactly is dropped A docstring is the first statement of a module, class or function when that statement is a bare string literal. The compiler normally stores it as the code object's `__doc__`. At level 2 it stores nothing, and the attribute reads back as `None`. Everything else about the string is unremarkable: a string literal anywhere else in the body is a perfectly ordinary constant and survives. Type annotations survive. Comments were never in the bytecode to begin with, so there is nothing for the flag to remove. You can observe the transformation without any flags at all, because the built-in `compile()` takes the same optimization level as an argument: ```python src = 'def f():\n "the docstring"\n return 1\n' ns = {} exec(compile(src, "<demo>", "exec", optimize=2), ns) print(ns["f"].__doc__) # None ``` ## Why the blast radius is bigger than it looks The level applies to every module compiled in the process, not just to your own code. A dependency deep in the tree that reads `__doc__` will see `None` too, and it has no way to opt out. Three families of breakage show up in practice. **Introspection.** `help()` and the `pydoc` module have nothing to print but signatures. For an interactive tool or a support workflow that leans on `help()`, that is a real regression. **Docstring-driven tests.** `doctest` scans `__doc__` for interactive examples. Under `-OO` it finds none — and, importantly, it does not *fail*; it reports that zero tests ran. A suite that silently drops to zero tests is worse than one that errors. **Docstrings used as data.** Some libraries parse a docstring into something functional: usage text for a command-line interface, a description attached to an API operation, help strings assembled into a schema. Those features do not degrade gracefully; they produce empty or malformed output, often far from the flag that caused it. ## The cost/benefit is thinner than people expect The pitch for `-OO` is memory: docstrings are strings held for the life of the process, and dropping them shrinks the resident set of a large dependency tree by a few megabytes. That can matter in a constrained container or a function-as-a-service image, and it is the only honest reason to reach for it. It is not a speed flag — nothing runs faster because a string was not loaded. Weigh it against the fact that a level-2 process is not the process your tests ran in unless CI also runs at level 2. If you adopt `-OO`, adopt it everywhere, and treat "does anything in our dependency graph read `__doc__`?" as a question to answer before shipping rather than after. ## Caching and reversibility Cached bytecode is written per level, with `opt-1` and `opt-2` in the file name, and level 0 has no tag at all. That means running at level 2 does not poison the caches used by an unoptimized run: drop the flag and the interpreter reads (or rebuilds) the untagged cache and the docstrings are back. Nothing is destroyed on disk, so the failure mode is a confusing runtime, not a corrupted checkout. ## What a good answer sounds like Name the level, name the single extra thing it removes, and name a concrete casualty — `help()`, `doctest`, or a docstring-driven CLI. Then add the judgement: the only real payoff is memory, the risk is behaviour you did not test, and if you use it, the whole pipeline uses it. ## A related lever that is not a flag Because `compile()` exposes the level directly, a tool that builds code objects itself — a template engine, a plugin loader, a cache of generated functions — chooses its own level independent of how the interpreter was started. That is occasionally useful (generated helpers whose docstrings nobody will ever read) and occasionally a surprise, because such code does not follow the process-wide setting unless the caller passes it along. The same argument exists on `py_compile.compile()`, and the `compileall` command line takes `-o` to write caches for a chosen level ahead of time. ## Docstrings are not the only introspection casualty Worth saying explicitly at interview: the attribute is `None`, not missing. Code written as `func.__doc__.strip()` raises `AttributeError` under level 2 rather than returning an empty string, and code written as `func.__doc__ or ""` degrades quietly. When you harden a library against being imported into a level-2 process, that defensive `or ""` is the shape you want — never assume a docstring is present just because you wrote one.
- Are docstrings the only strings -OO removes from a module?Yes. Only the docstring position is special — the first statement of a module, class or function when it is a bare string literal. Every other string literal is an ordinary constant and is stored normally, so log messages, format templates and lookup keys are untouched. Annotations are also untouched, and comments were never compiled into bytecode in the first place.
- What is the actual benefit that would justify running -OO?Memory. Docstrings across a large dependency tree are strings held for the lifetime of the process, and dropping them can shave a few megabytes of resident set — worth considering in a tightly constrained container or a small function image. It buys no speed. If you take it, run tests at level 2 as well, or you are shipping a configuration nobody exercised.
- How would you find out whether -OO is safe for a given codebase?Run the whole test suite at level 2 and compare, then grep the tree — your code and the installed dependencies — for reads of `__doc__` and for any tool that builds user-facing text out of docstrings. Check whether `doctest` contributes to your coverage, because under level 2 it reports zero tests rather than failing, which is easy to miss.
saying these in an interview costs you the question
- Says -OO strips comments from the source
- Claims -OO removes type annotations or hints
- Believes -OO makes the interpreter measurably faster
- Thinks the docstrings are permanently lost from disk
- Expects doctest to error rather than silently find zero tests
- Assumes -OO affects only the main script, not imported modules