Python is the lingua franca of data work, scripting, and most modern backends. The combination of dynamic typing and a giant standard library makes it deeply productive — and gives bugs a lot of places to hide. The same `for` loop that's perfect for a hundred iterations can quietly chew through gigabytes if the inner generator wasn't designed for streaming.
Python's bug classes cluster in a few places: SQL and shell injection where f-strings replace parameter binding, mutable default arguments (`def f(x, items=[])`) sharing state across calls, race conditions in async code that doesn't actually use `await`, and integer-vs-float surprises when JSON serializers cross language boundaries.
Type-checked Python — `mypy`, `pyright`, `pydantic` for runtime validation — narrows the surface dramatically. Code review still has to catch the cases the type system doesn't see: business-logic errors, race conditions, missing input validation, and the perennial "this dict has more keys than the schema admits" bug.
Common pitfalls
F-string SQL queries
Python's f-strings are concatenation, not parameter binding. Driver placeholders (`%s`, `?`, `$1`) are the safe path.
Mutable default arguments
A `[]` or `{}` in a function signature is created once and reused. If the function mutates it, subsequent calls see the old value.
Async without await
Coroutines need to be awaited or scheduled. A bare call returns a coroutine object that does nothing.