skip to content

What does CPython's `-X dev` development mode switch on at runtime?

level: middleimportance: must knowfreq 38%

answer

  1. A runtime mode, not a build
  2. One flag that bundles several switches
  3. Warnings, allocator, faulthandler, asyncio
  4. Environment variable is the inheritable form
  5. sys.flags tells you it is on

basics

~10 s

Running python -X dev (or setting PYTHONDEVMODE=1) enables the default warning filter so DeprecationWarning and ResourceWarning appear, debug hooks in the memory allocator, faulthandler, asyncio debug mode, and stricter encoding-argument checks.

solid answer

~40 s

Development mode is a runtime mode on an ordinary release interpreter, enabled with `python -X dev` or `PYTHONDEVMODE=1`. It bundles switches that already exist separately: the default warning filter (as `-W default`), so `DeprecationWarning`, `PendingDeprecationWarning`, `ImportWarning` and `ResourceWarning` become visible wherever they are raised; debug hooks on the memory allocators (as `PYTHONMALLOC=debug`) that detect buffer overruns, use-after-free and allocator misuse in C code; `faulthandler`; asyncio debug mode (as `PYTHONASYNCIODEBUG=1`); eager checking of the `encoding` and `errors` arguments of encode/decode calls; and `sys.flags.dev_mode` set to `True`. It does **not** require a debug build, does not enable `tracemalloc`, and does not turn warnings into errors — `-W error` does that.

code

python · 4 lines
python
import sys

# False on a normal run; True under `python -X dev` or PYTHONDEVMODE=1
print("dev mode:", sys.flags.dev_mode)

go deeper

for a junior

Be ready to name the flag and one thing it does: python -X dev makes hidden warnings such as unclosed-file ResourceWarning show up. Knowing that it is a development and CI setting, not a production one, is enough at this level.

for a middle

Explain the mechanics: which warnings the default filter unhides, that the allocator debug hooks come from PYTHONMALLOC=debug, that asyncio debug mode is included, and that sys.flags.dev_mode reports it. Be precise that it shows warnings rather than raising them.

for a senior

Show that you have run it against a real service: what you keep on permanently, what you enable only for a reproduction, and how you combine it with memory tracing to get allocation tracebacks. Interviewers expect you to name the cost, not just the benefits.

for a principal

Own the policy: development mode is a bundle of independent switches, so decide per environment which parts you want, how the setting propagates through a process tree, and how deprecations surfaced in CI feed your interpreter-upgrade plan.

**What development mode is.** Python's development mode is a *runtime* mode, not a build. You get it with `python -X dev script.py`, or by exporting `PYTHONDEVMODE=1`. Any ordinary release interpreter supports it: you do not need a `--with-pydebug` build of CPython, the ABI is unchanged, and every C extension you already have keeps working. What you pay is performance, which is why it belongs in local development, CI and diagnosis rather than in a production process. **What it turns on.** Six effects, every one of which also exists as its own switch: 1. **The default warning filter**, exactly as if you had passed `-W default`. This is the effect people notice first. CPython's normal filters ignore `DeprecationWarning` (except when it is triggered in `__main__`), `PendingDeprecationWarning`, `ImportWarning` and `ResourceWarning`; development mode displays all of them, including the ones raised from inside libraries you imported. 2. **Debug hooks on the memory allocators**, the same thing as `PYTHONMALLOC=debug`. Each block gets padding bytes on both sides and a fill pattern, and freed memory is overwritten. CPython then detects buffer overflows and underflows, use-after-free, freeing a block with a different allocator than the one that allocated it, and calls made without holding the GIL. A C extension bug that used to be a random crash three functions later becomes an immediate, located abort. 3. **`faulthandler`**, as if you had passed `-X faulthandler`, so a hard crash prints a Python traceback rather than only a signal. 4. **asyncio debug mode**, the same as `PYTHONASYNCIODEBUG=1`. asyncio then reports coroutine objects that were created and never awaited, logs callbacks and I/O handlers that hold the event loop past its slow-callback threshold (100 ms by default), records where each coroutine was created so a "Task was destroyed but it is pending" message can point at real source, and checks that calls come from the thread that owns the loop. 5. **`sys.flags.dev_mode` set to `True`**, so your own code — or a library — can branch on it and add extra checking of its own. 6. **Stricter argument checking on text encoding and decoding.** For speed, CPython normally validates the `errors` argument only at the first encoding error, and can skip the `encoding` argument entirely for an empty string; development mode checks both eagerly, so a typo like `errors="strick"` fails on the first call instead of on the one input that happens to contain a non-ASCII byte. Development mode also makes `io.IOBase` destructors log exceptions raised by `close()` instead of discarding them. **What it does not do.** It does not enable `tracemalloc` — that is the separate `-X tracemalloc=N` / `PYTHONTRACEMALLOC=N`, and combining the two is what gives a `ResourceWarning` the traceback of where the leaked object was allocated. It does not enable `EncodingWarning`, which needs `-X warn_default_encoding`. It does not turn warnings into errors: it *displays* them, and `-W error::DeprecationWarning` is what makes them fail. It does not add the C-level assertions of a debug build, and it is nowhere near as slow as one. **Scope and inheritance.** A `-X` option applies to the process you launch and is not passed on to children. The environment is inherited, though, so when you want a whole process tree in development mode — a runner spawning workers, or a `multiprocessing` pool using the `spawn` or `forkserver` start method — export `PYTHONDEVMODE=1` instead of passing the flag on one command line. At runtime, `sys.flags.dev_mode` tells you which mode you are in, and it is the honest way for a health endpoint or a start-up check to report it. **Why the mode exists as a bundle.** Each of its parts predates it, and before development mode existed you had to remember five separate incantations to get a strict run. Bundling them made "run it strictly" a single, memorable flag that a CI job or a new contributor can adopt without a checklist. The corollary matters just as much in production work: because they are still independent switches, you can take one without the others — `-X faulthandler` alone is cheap enough to run permanently, while the allocator hooks are not. **Why interviewers ask.** The answer separates people who have only written Python from people who have operated it. The follow-through is knowing where the mode belongs: on in development and CI, where the slowdown is irrelevant and the signal is valuable; off in production, where the allocator hooks and asyncio bookkeeping cost measurable latency and a surfaced third-party deprecation is noise rather than action.

  • Does `-X dev` make the interpreter as slow as a debug build of CPython?
    No. A `--with-pydebug` build compiles in C-level assertions, extra bookkeeping and reference-count tracing, and is typically several times slower; development mode is a set of runtime switches on a normal release build. It still costs real time — the allocator debug hooks touch every allocation and free, and asyncio debug mode captures stacks — so it is a development and CI setting, not a production one.
  • How would you put a whole process tree into development mode, not just the parent?
    Export `PYTHONDEVMODE=1` rather than passing `-X dev`. A `-X` option applies only to the process you launch, while the environment is inherited by children, so workers started through `subprocess` or by a `multiprocessing` pool using the `spawn` or `forkserver` start method pick it up automatically. Each process can confirm it with `sys.flags.dev_mode`.
  • Development mode showed a ResourceWarning but not where the object was created. What do you add?
    `-X tracemalloc=25` (or `PYTHONTRACEMALLOC=25`). Development mode does not enable `tracemalloc`, and without it the warning can only describe the object being collected. With memory tracing on, the `ResourceWarning` carries the traceback of the allocation site, which is the line that actually opened the file or socket. The frame limit is a cost knob — deeper is more useful and slower.

It is the diagnostic mode on an engine test bench: extra sensors on every moving part, so faults announce themselves instead of showing up as a breakdown later. You would not drive with the bench attached.

saying these in an interview costs you the question

  • Claiming `-X dev` requires a debug build of CPython
  • Saying it turns warnings into errors
  • Thinking it enables tracemalloc
  • Assuming child processes inherit the `-X` flag
  • Recommending it as a permanent production setting
  • Confusing it with the `-O` optimization flags

context