What are common Python anti-patterns to avoid in production code?
Quick Answer
Common ones: mutable default arguments (shared state leak across calls), bare `except:` clauses (swallow everything including `SystemExit`/`KeyboardInterrupt`), using `pickle`/`eval` on untrusted data, wildcard imports (`from module import *`, polluting the namespace and hiding where names come from), catching exceptions just to `pass` silently, and using a mutable class attribute where an instance attribute was intended.
Detailed Answer
1. Mutable default arguments
# BAD -- shared across every call that omits the argument
def add_item(item, bucket=[]):
bucket.append(item)
return bucket
# GOOD
def add_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
Covered in depth in the Fundamentals topic — worth repeating here as one of the most common real-world bugs traced back to a single anti-pattern.
2. Bare except: (or overly broad exception handling)
# BAD
try:
risky_operation()
except:
pass # swallows EVERYTHING, including typos (NameError) and Ctrl+C
Silently swallowing all exceptions hides real bugs and makes production
issues nearly impossible to diagnose — always catch specific exceptions,
and if you must log-and-continue at a boundary, log the actual exception
(logger.exception(...)), don't discard it.
3. Mutable class attributes intended as instance attributes
# BAD -- shared across every instance!
class ShoppingCart:
items = []
def add(self, item):
self.items.append(item)
cart1 = ShoppingCart()
cart2 = ShoppingCart()
cart1.add("apple")
cart2.items # ['apple'] -- BUG: cart2 sees cart1's item!
# GOOD -- instance attribute, set per-object in __init__
class ShoppingCart:
def __init__(self):
self.items = []
items = [] at class scope creates one list shared by every
instance; each instance needs its own list created in __init__.
4. Wildcard imports
# BAD -- pollutes the namespace, unclear where names come from
from mymodule import *
value = some_function() # which module defined this? impossible to tell by reading
# GOOD -- explicit imports, or a namespaced import
from mymodule import some_function
import mymodule
mymodule.some_function()
import * makes it impossible to tell, just by reading the code, which
module a given name came from — it also risks silently shadowing
existing names, and static analysis tools/IDEs can't reliably follow it.
5. Catching an exception just to silence it
# BAD -- hides real failures, produces confusing downstream behavior
try:
result = fetch_data()
except Exception:
result = None # caller now has no idea WHY this is None
# GOOD -- handle it meaningfully, or let it propagate
try:
result = fetch_data()
except ConnectionError:
logger.warning("fetch_data failed, using cached value")
result = get_cached_value()
Swallowing an exception into a generic fallback value without logging or distinguishing why it failed turns a diagnosable failure into a mysterious downstream symptom.
6. Using type() instead of isinstance() for type checks
# BAD -- breaks for subclasses
if type(obj) == list:
...
# GOOD -- respects inheritance/polymorphism
if isinstance(obj, list):
...
type(obj) == list fails for any subclass of list, defeating
polymorphism; isinstance (which also accepts a tuple of types) is
almost always what's actually intended.
7. String concatenation in a loop instead of str.join
# BAD -- O(n^2): each += creates a new string, copying everything so far
result = ""
for item in items:
result += str(item)
# GOOD -- O(n): join builds the final string in one pass
result = "".join(str(item) for item in items)
Since strings are immutable, repeated += in a loop recreates the
entire string on every iteration — quadratic behavior that str.join
avoids entirely.
Interview-ready summary: Most Python anti-patterns share a common
thread — a subtle mismatch between what the code visually appears to do
and what actually happens under the hood (mutable defaults/class
attributes shared unexpectedly, bare except: hiding real failures,
type() breaking polymorphism). Recognizing and avoiding this small,
well-known set of patterns eliminates a large share of real-world Python
bugs.