COMMON PYTHON FIXES

Solve a Python Problem

Search a symptom, inspect broken code, and follow a full guide or a quick answer. Each guide explains the failure, its cause, the fix, and how to verify it.

Find a likely fix

Paste the last line of a Python error, or type what went wrong. This tool checks it in your browser and suggests relevant guides. Compare the cause in the guide with your own code before changing anything.

Your text stays in this browser. For a traceback, the final error line is usually enough; leave out file paths or private data.

Browse guides and quick answers

Search specific wording or filter by topic. Full guides include a reproducible failure, explanation, correction, and practice.

402 results

Functions & classesFULL GUIDE · 8 MIN READ

Fix UnboundLocalError: Local Variable Before Assignment in Python

A function assigns to a name, so Python treats it as local throughout that function; reading it before a value is assigned raises UnboundLocalError. Pass the current value in and return the update, or initialize a branch-dependent local before reading it.

Logic & loopsFULL GUIDE · 6 MIN READ

How to Fix ZeroDivisionError in Python

Dividing by zero stops a calculation. Trace where the divisor came from, decide what zero means for your program, and test that rule instead of hiding the error.

Basics & inputFrom Python History

Assuming the name means snake

The code runs, but the historical claim is false.

See the fix
COMMON MISTAKE
print("Python was named after a snake.")
FIX
print("Python was named after Monty Python.")

A program can run successfully and still print incorrect information. Check the fact as well as the syntax.

Basics & inputFrom Python History

Mixing up 1991 and 2008

The code runs, but it confuses two milestones.

See the fix
COMMON MISTAKE
print("Python 3.0 was first shared in 1991.")
FIX
print("Python was public in 1991; Python 3.0 arrived in 2008.")

Python's first public sharing and the Python 3.0 release happened at different times.

Basics & inputFrom Hello, World!

Unquoted text

NameError: Python looks for a variable named Hello.

See the fix
COMMON MISTAKE
print(Hello)
FIX
print("Hello")

Put literal text inside quotes so Python treats it as a string.

Basics & inputFrom Hello, World!

Missing parentheses

SyntaxError in Python 3.

See the fix
COMMON MISTAKE
print "Hello"
FIX
print("Hello")

print is a function, so its argument goes inside parentheses.

Basics & inputFrom Variables and Types

Case-sensitive names

NameError because Name and name are different names.

See the fix
COMMON MISTAKE
name = "Maya"
print(Name)
FIX
name = "Maya"
print(name)

Use exactly the same spelling and capitalization each time.

Basics & inputFrom Variables and Types

Mixing text and numbers

TypeError when joining a string and an integer.

See the fix
COMMON MISTAKE
age = 12
print("Age: " + age)
FIX
age = 12
print("Age: " + str(age))

Convert the number to a string before concatenating it with text.

Strings & textFrom String Formatting

Unknown placeholder

NameError because username has not been defined.

See the fix
COMMON MISTAKE
name = "Maya"
print(f"Hi, {username}")
FIX
name = "Maya"
print(f"Hi, {name}")

A placeholder must refer to a defined name or valid expression.

Strings & textFrom Basic String Operations

Index outside a string

IndexError because valid positive indexes are 0, 1, and 2.

See the fix
COMMON MISTAKE
word = "cat"
print(word[3])
FIX
word = "cat"
print(word[2])

The last positive index is one less than the string length.

Strings & textFrom Basic String Operations

Changing a character in place

TypeError because strings cannot be changed in place.

See the fix
COMMON MISTAKE
word = "cat"
word[0] = "b"
FIX
word = "cat"
word = "b" + word[1:]
print(word)

Construct a new string and assign it to the variable.

Logic & loopsFrom Loops

A while loop never progresses

The condition stays true, so the loop keeps printing 0.

See the fix
COMMON MISTAKE
count = 0
while count < 3:
    print(count)
FIX
count = 0
while count < 3:
    print(count)
    count += 1

Update the value tested by the condition.

Functions & classesFrom Functions

Printing instead of returning

The function prints 8, but result is None.

See the fix
COMMON MISTAKE
def double(value):
    print(value * 2)
result = double(4)
print(result)
FIX
def double(value):
    return value * 2
result = double(4)
print(result)

Use return when callers need to store or reuse the calculated value.

Functions & classesFrom Functions

Missing required argument

TypeError because name was not supplied.

See the fix
COMMON MISTAKE
def greet(name):
    print(f"Hi, {name}")
greet()
FIX
def greet(name):
    print(f"Hi, {name}")
greet("Maya")

Provide one argument for each required parameter.

Functions & classesFrom Classes and Objects

Omitting self in a method

TypeError because Python passes the instance automatically.

See the fix
COMMON MISTAKE
class Book:
    def summary():
        return "A book"
book = Book()
print(book.summary())
FIX
class Book:
    def summary(self):
        return "A book"
book = Book()
print(book.summary())

Instance methods need self as their first parameter.

Functions & classesFrom Classes and Objects

Forgetting self on an attribute

AttributeError because no title attribute was stored on the object.

See the fix
COMMON MISTAKE
class Book:
    def __init__(self, title):
        title = title
book = Book("Python Notes")
print(book.title)
FIX
class Book:
    def __init__(self, title):
        self.title = title
book = Book("Python Notes")
print(book.title)

Assign to self.title to save the value on the instance.

CollectionsFrom Dictionaries

Looking up a missing key

KeyError for score.

See the fix
COMMON MISTAKE
learner = {"name": "Maya"}
print(learner["score"])
FIX
learner = {"name": "Maya"}
print(learner.get("score", 0))

Use get with a fallback if the key may be absent.

CollectionsFrom Dictionaries

Using a list-style index

KeyError because 0 is not a key.

See the fix
COMMON MISTAKE
learner = {"name": "Maya"}
print(learner[0])
FIX
learner = {"name": "Maya"}
print(learner["name"])

Dictionary lookup uses keys, not numeric positions.

Files & setupFrom Modules and Packages

Using a module before importing it

NameError if math has not been imported in this run.

See the fix
COMMON MISTAKE
print(math.sqrt(9))
FIX
import math
print(math.sqrt(9))

Import the module before using its name.

Files & setupFrom Modules and Packages

Shadowing the module name

AttributeError because math now refers to the integer 4.

See the fix
COMMON MISTAKE
import math
math = 4
print(math.sqrt(9))
FIX
import math
number = 4
print(math.sqrt(9))

Avoid reusing an imported module's name for another value.

Basics & inputFrom Input and Output

Converting nonnumeric text

ValueError because the text is not a valid integer.

See the fix
COMMON MISTAKE
entered_age = "seventeen"
print(int(entered_age))
FIX
entered_age = "17"
print(int(entered_age))

int accepts text that represents a valid whole number.

ProjectsFrom Project: Number Guessing Game

Assigning instead of comparing

SyntaxError because = is assignment, not comparison.

See the fix
COMMON MISTAKE
if guess = secret:
    print('Correct')
FIX
if guess == secret:
    print('Correct')

Use == when asking whether two values are equal.

ProjectsFrom Project: Number Guessing Game

Reversing the low hint

The player receives a misleading hint.

See the fix
COMMON MISTAKE
if guess < secret:
    return 'Too high'
FIX
if guess < secret:
    return 'Too low'

Compare the guessed number to the secret from the player's perspective.

CollectionsFrom Tuples and Sets

Expecting a set to preserve duplicates

It prints False because a set has only two distinct values.

See the fix
COMMON MISTAKE
print(len(set([1, 1, 2])) == 3)
FIX
print(len(set([1, 1, 2])) == 2)

A set keeps one copy of each equal value.

CollectionsFrom Tuples and Sets

Using {} for an empty set

AttributeError: a dictionary has no add method.

See the fix
COMMON MISTAKE
items = {}
items.add('python')
FIX
items = set()
items.add('python')

Construct an empty set with set().

Files & setupFrom Exceptions

Catching the wrong exception

ZeroDivisionError still stops the program.

See the fix
COMMON MISTAKE
try:
    print(4 / 0)
except ValueError:
    print('Bad number')
FIX
try:
    print(4 / 0)
except ZeroDivisionError:
    print('Cannot divide by zero')

The except type must match the error that may be raised.

Files & setupFrom Exceptions

Using a bare except

Unrelated bugs are hidden behind a vague message.

See the fix
COMMON MISTAKE
try:
    risky_code()
except:
    print('Something failed')
FIX
try:
    number = float('bad')
except ValueError:
    print('Invalid number')

Catch an expected error by name so other failures remain visible.

Files & setupFrom Reading and Writing Files

Reading before writing

FileNotFoundError if the virtual file has not been created in this run.

See the fix
COMMON MISTAKE
with open('new_note.txt', 'r', encoding='utf-8') as file:
    print(file.read())
FIX
with open('new_note.txt', 'w', encoding='utf-8') as file:
    file.write('Ready')
with open('new_note.txt', 'r', encoding='utf-8') as file:
    print(file.read())

Create the file before trying to read it in a fresh exercise run.

Files & setupFrom Reading and Writing Files

Expecting append from write mode

The old file contents disappear.

See the fix
COMMON MISTAKE
with open('note.txt', 'w', encoding='utf-8') as file:
    file.write('second')
FIX
with open('note.txt', 'a', encoding='utf-8') as file:
    file.write('second')

Choose a only when the new text should be added at the end.

Files & setupFrom Installing Python and Virtual Environments

Typing a terminal command into Python code

SyntaxError in the browser editor: this is a shell command.

See the fix
COMMON MISTAKE
python -m venv .venv
FIX
import sys
print(sys.version_info.major)

Use the browser editor for Python statements and a local terminal for setup commands.

Files & setupFrom Installing Python and Virtual Environments

Assuming a browser version is the local version

The claim may be false because the browser runtime is separate.

See the fix
COMMON MISTAKE
print('My laptop uses Python 3.13')
FIX
import sys
print(sys.version_info.major, sys.version_info.minor)

Inspect each interpreter in its own environment rather than assuming they match.

CollectionsFrom List Comprehensions

Filtering odd values by accident

The result contains doubled odd values, such as [2, 6] for [1, 2, 3, 4].

See the fix
COMMON MISTAKE
[n * 2 for n in numbers if n % 2]
FIX
[n * 2 for n in numbers if n % 2 == 0]

The remainder is 1 for odd numbers, which is truthy. Compare explicitly with zero for even values.

CollectionsFrom List Comprehensions

Putting the filter before the loop

SyntaxError: expected else after the conditional expression.

See the fix
COMMON MISTAKE
[n * 2 if n % 2 == 0 for n in numbers]
FIX
[n * 2 for n in numbers if n % 2 == 0]

A trailing if filters items. An if before for is a different expression form and needs an else branch.

CollectionsFrom List Comprehensions

Keeping unwanted values while trying to filter

Odd inputs become zero, so the output still has one element per input instead of only even values.

See the fix
COMMON MISTAKE
[n if n % 2 == 0 else 0 for n in numbers]
FIX
[n for n in numbers if n % 2 == 0]

Use a trailing if when items should disappear. Use an if/else expression when every item must produce an output.

Functions & classesFrom Function Arguments, Return Values, and Scope

Trying to read a local variable outside its function

NameError: name 'total' is not defined.

See the fix
COMMON MISTAKE
def total_with_tax(price):
    total = price * 1.2
total_with_tax(20)
print(total)
FIX
def total_with_tax(price):
    total = price * 1.2
    return total
print(total_with_tax(20))

Return the local value. The call expression then provides it to the caller.

Functions & classesFrom Function Arguments, Return Values, and Scope

Printing instead of returning

The output contains 8 and then None.

See the fix
COMMON MISTAKE
def double(n):
    print(n * 2)
result = double(4)
print(result)
FIX
def double(n):
    return n * 2
result = double(4)
print(result)

Printing is a side effect. A return statement makes the value available for assignment and further calculation.

Files & setupFrom Read and Write JSON and CSV

Adding a CSV number as text

TypeError: can only concatenate str (not int) to str.

See the fix
COMMON MISTAKE
row = next(csv.DictReader(StringIO('score\n7\n')))
print(row['score'] + 1)
FIX
row = next(csv.DictReader(StringIO('score\n7\n')))
print(int(row['score']) + 1)

Every CSV cell starts as text. Convert with int only when the data should contain a whole number.

CollectionsFrom Loop with enumerate() and zip()

Treating enumerate's first value as the item

AttributeError because topic is the integer index.

See the fix
COMMON MISTAKE
for topic, number in enumerate(['loops'], start=1):
    print(topic.upper())
FIX
for number, topic in enumerate(['loops'], start=1):
    print(topic.upper())

enumerate gives index first, item second. Name the unpacked values in that order.

CollectionsFrom Loop with enumerate() and zip()

Silently dropping an extra score

The second score is lost and no exception explains the mismatch.

See the fix
COMMON MISTAKE
list(zip(['Ada'], [8, 5]))
FIX
list(zip(['Ada'], [8, 5], strict=True))

Use strict=True when both sequences must have matching lengths; consume the iterator to trigger the check.

CollectionsFrom Dictionary and Set Comprehensions

Using braces without a colon for a mapping

The result is a set of words with no lengths.

See the fix
COMMON MISTAKE
lengths = {word for word in ['cat', 'go']}
FIX
lengths = {word: len(word) for word in ['cat', 'go']}

Include key: value before for when the result should be a dictionary.

Functions & classesFrom Test Python Functions with Assertions

Printing a result without checking it

The terminal shows a value, but a wrong value does not fail a repeatable test.

See the fix
COMMON MISTAKE
print(double(4))
FIX
assert double(4) == 8

State the expected value explicitly so a regression fails when the test runs.

Functions & classesFrom Test Python Functions with Assertions

Testing only the easiest case

An implementation that always returns value passes even though it fails outside the range.

See the fix
COMMON MISTAKE
assert clamp(5, 0, 10) == 5
FIX
assert clamp(5, 0, 10) == 5
assert clamp(-1, 0, 10) == 0
assert clamp(11, 0, 10) == 10

Choose inputs that exercise each branch, including boundaries and errors.

Functions & classesFrom Test Python Functions with Assertions

Using assert to validate user input

Running Python with -O can remove the check and allow a negative age through.

See the fix
COMMON MISTAKE
def save_age(age):
    assert age >= 0
    return age
FIX
def save_age(age):
    if age < 0:
        raise ValueError('age must be non-negative')
    return age

Explicit validation runs regardless of optimization; reserve assertions for checks whose removal does not change required behavior.

Files & setupFrom Dates and Timedeltas in Python

Editing the day characters directly

It prints an invalid February 30 instead of crossing into March.

See the fix
COMMON MISTAKE
start = '2024-02-28'
print(start[:-2] + str(int(start[-2:]) + 2))
FIX
start = date.fromisoformat('2024-02-28')
print((start + timedelta(days=2)).isoformat())

Calendar arithmetic handles the length of each month and leap years.

ProjectsFrom Project: Contact Book

Normalizing names only when saving

Searching for ADA LOVELACE misses an existing contact.

See the fix
COMMON MISTAKE
book[name.strip().casefold()] = record
found = book.get(search_name)
FIX
book[name.strip().casefold()] = record
found = book.get(search_name.strip().casefold())

Apply the same rule to both the saved and searched name.

ProjectsFrom Project: Contact Book

Assuming names identify real people uniquely

Adding a second person with the same name overwrites the first.

See the fix
COMMON MISTAKE
book[person_name] = email  # two people can share this name
FIX
# For real data, use a separate stable contact ID as the key.

Normalized names are convenient for this exercise but cannot serve as reliable unique identifiers in a real address book.

CollectionsFrom Sort Python Lists and Dictionaries with key

Trying to compare whole dictionaries

TypeError: '<' is not supported between instances of 'dict' and 'dict'.

See the fix
COMMON MISTAKE
sorted([{'price': 12}, {'price': 3}])
FIX
sorted([{'price': 12}, {'price': 3}], key=lambda item: item['price'])

Choose the numeric field that defines the order; dictionaries themselves have no default less-than ordering.

CollectionsFrom Sort Python Lists and Dictionaries with key

Assigning the result of list.sort()

The call prints None, and products was changed in place.

See the fix
COMMON MISTAKE
products = [{'price': 12}, {'price': 3}]
ordered = products.sort(key=lambda item: item['price'])
print(ordered)
FIX
ordered = sorted(products, key=lambda item: item['price'])

Use sorted() when you need a new list and must preserve the input.

CollectionsFrom Sort Python Lists and Dictionaries with key

Sorting numeric prices as text

The text '10' comes before '2' lexicographically.

See the fix
COMMON MISTAKE
sorted(['10', '2'])
FIX
sorted(['10', '2'], key=int)

Convert numeric text in the key function when numeric order is intended.

Files & setupFrom Work with File Paths using pathlib

Writing before the parent folder exists

FileNotFoundError if notes has not been created.

See the fix
COMMON MISTAKE
Path('notes/day-1.txt').write_text('hello', encoding='utf-8')
FIX
path = Path('notes/day-1.txt')
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('hello', encoding='utf-8')

Create the containing folder first; parents=True also creates missing levels above it.

Files & setupFrom Work with File Paths using pathlib

Assuming write_text appends

The file contains only second.

See the fix
COMMON MISTAKE
path.write_text('first', encoding='utf-8')
path.write_text('second', encoding='utf-8')
FIX
path.write_text('first', encoding='utf-8')
with path.open('a', encoding='utf-8') as file:
    file.write('second')

Use append mode explicitly when existing text must be kept.

Strings & textFrom Python Regular Expressions: search and findall

Using a non-raw boundary pattern

The Python string contains backspace characters rather than regex word-boundary escapes, so the expected code is missed.

See the fix
COMMON MISTAKE
re.findall("\bORD-[0-9]{4}\b", "ORD-1234")
FIX
re.findall(r"\bORD-[0-9]{4}\b", "ORD-1234")

Prefix a regex pattern with r so backslashes reach the regular-expression parser as intended.

Strings & textFrom Python Regular Expressions: search and findall

Accepting only the start of an invalid code

The expression is True even though a fifth digit remains.

See the fix
COMMON MISTAKE
re.match(r'ORD-[0-9]{4}', 'ORD-12345') is not None
FIX
re.fullmatch(r'ORD-[0-9]{4}', 'ORD-12345') is not None

Use fullmatch to validate a whole string; use word boundaries when extracting a whole code from a sentence.

Strings & textFrom Python Regular Expressions: search and findall

Expecting search to return text directly

AttributeError because search returns a Match object, not a string.

See the fix
COMMON MISTAKE
re.search(r'cat', 'A cat naps.').upper()
FIX
match = re.search(r'cat', 'A cat naps.')
print(match.group().upper() if match else 'none')

Check for None, then call group() to get matched text.

Logic & loopsFrom Iterators and Generators in Python

Returning a list instead of yielding values

The values are correct, but the function returns a list rather than a generator.

See the fix
COMMON MISTAKE
def even_numbers(limit):
    return [n for n in range(limit) if n % 2 == 0]
FIX
def even_numbers(limit):
    for n in range(limit):
        if n % 2 == 0:
            yield n

Use yield when the task requires items to be produced on demand.

Logic & loopsFrom Iterators and Generators in Python

Reusing an already consumed generator

The second print displays [] because the first pass consumed values.

See the fix
COMMON MISTAKE
values = (n * n for n in range(3))
print(list(values))
print(list(values))
FIX
def squares():
    return (n * n for n in range(3))
print(list(squares()))
print(list(squares()))

Call the generator-producing function again to make a fresh iterator.

Logic & loopsFrom Iterators and Generators in Python

Calling next after exhaustion without a default

The second request raises StopIteration.

See the fix
COMMON MISTAKE
items = iter([1])
next(items)
next(items)
FIX
items = iter([1])
next(items)
print(next(items, None))

Use a default or a for loop if exhaustion is expected.

Functions & classesFrom Python dataclasses: Fields, Defaults, and asdict

Using a mutable list as a direct default

Modern Python raises ValueError at class creation because a mutable list is an unsafe default.

See the fix
COMMON MISTAKE
@dataclass
class StudyTask:
    title: str
    tags: list[str] = []
FIX
@dataclass
class StudyTask:
    title: str
    tags: list[str] = field(default_factory=list)

A factory supplies an independent list each time an instance is created.

Functions & classesFrom Python dataclasses: Fields, Defaults, and asdict

Putting a required field after a default

TypeError: non-default argument 'title' follows default argument.

See the fix
COMMON MISTAKE
@dataclass
class StudyTask:
    done: bool = False
    title: str
FIX
@dataclass
class StudyTask:
    title: str
    done: bool = False

Keep required fields before optional fields with defaults.

Functions & classesFrom Python dataclasses: Fields, Defaults, and asdict

Assuming an annotation validates data

The instance can still be created with an integer title.

See the fix
COMMON MISTAKE
task = StudyTask(123)
# Assume title must be a str at runtime.
FIX
if not isinstance(title, str):
    raise TypeError('title must be text')
task = StudyTask(title)

Use explicit validation when a runtime guarantee matters; an annotation alone is descriptive.

CollectionsFrom Count and Group Data with Counter and defaultdict

Treating Counter as text normalization

Bug and bug are counted as two different labels.

See the fix
COMMON MISTAKE
Counter(['Bug', 'bug'])
FIX
Counter(label.strip().lower() for label in ['Bug', 'bug'])

Normalize the data before counting when case and surrounding whitespace should not matter.

Functions & classesFrom Write Clearer Functions with Python Type Hints

Claiming a possibly absent result is always a string

The function can return None although its annotation promises str.

See the fix
COMMON MISTAKE
def lookup(names: list[str], wanted: str) -> str:
    return next((name for name in names if name == wanted), None)
FIX
def lookup(names: list[str], wanted: str) -> str | None:
    return next((name for name in names if name == wanted), None)

Include None in the return hint whenever a normal branch can return it.

Functions & classesFrom Write Clearer Functions with Python Type Hints

Expecting an annotation to validate outside input

A runtime TypeError occurs inside the function; the hint did not convert '20'.

See the fix
COMMON MISTAKE
def age_next_year(age: int) -> int:
    return age + 1

age_next_year('20')
FIX
age = int('20')
print(age_next_year(age))

Convert or validate user input explicitly before calling a function that expects an integer.

Functions & classesFrom Write Clearer Functions with Python Type Hints

Using a bare container hint

Readers cannot tell what the list contains or what maps to each count.

See the fix
COMMON MISTAKE
def count_words(words: list) -> dict:
FIX
def count_words(words: list[str]) -> dict[str, int]:

Element, key, and value hints make the contract more useful.

Functions & classesFrom Write Python Decorators that Preserve Behavior

Forgetting to return the wrapper

The decorated name becomes None, so calling it raises TypeError.

See the fix
COMMON MISTAKE
def announce(func):
    def wrapper():
        return func()
    # no return wrapper
FIX
def announce(func):
    def wrapper():
        return func()
    return wrapper

The decorator itself must return the callable that replaces the original name.

Functions & classesFrom Write Python Decorators that Preserve Behavior

Discarding the original result

A function that returned a value now appears to return None.

See the fix
COMMON MISTAKE
def wrapper(*args, **kwargs):
    func(*args, **kwargs)
FIX
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)

Return the original result unless changing it is the deliberate purpose of the decorator.

Functions & classesFrom Write Python Decorators that Preserve Behavior

Losing the decorated function's name

The decorated function reports __name__ as wrapper.

See the fix
COMMON MISTAKE
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)
return wrapper
FIX
@wraps(func)
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)
return wrapper

functools.wraps keeps useful metadata and exposes __wrapped__.

Files & setupFrom Manage Resources with Python with Statements

Opening a file without a bounded lifetime

The handle may remain open if later code raises before close().

See the fix
COMMON MISTAKE
note = open('note.txt', 'w', encoding='utf-8')
note.write('draft')
FIX
with open('note.txt', 'w', encoding='utf-8') as note:
    note.write('draft')

with asks the file's context manager to close the handle on either exit path.

Files & setupFrom Manage Resources with Python with Statements

Forgetting finally in a generator manager

If the with body raises, the line after yield is skipped and cleanup is lost.

See the fix
COMMON MISTAKE
@contextmanager
def managed(log):
    log.append('open')
    yield
    log.append('close')
FIX
@contextmanager
def managed(log):
    log.append('open')
    try:
        yield
    finally:
        log.append('close')

Put cleanup in finally so it runs during exception propagation.

Files & setupFrom Manage Resources with Python with Statements

Silencing every failure as cleanup

A real write error can disappear without the caller noticing.

See the fix
COMMON MISTAKE
with suppress(Exception):
    write_important_note()
FIX
with note_writer('note.txt') as note:
    note.write('important')

Resource cleanup and error handling are separate decisions; allow unexpected errors to propagate.

Logic & loopsFrom Build Small Data Pipelines with itertools

Materializing everything before taking a few values

The entire readings iterator is consumed, even when only three totals are needed.

See the fix
COMMON MISTAKE
first = list(accumulate(readings))[:3]
FIX
first = list(islice(accumulate(readings), 3))

Place islice before list so upstream work stops at the requested count.

Logic & loopsFrom Build Small Data Pipelines with itertools

Treating groupby as global grouping

There are two separate ok groups, not one combined group.

See the fix
COMMON MISTAKE
from itertools import groupby
print([(key, list(group)) for key, group in groupby(['ok', 'fail', 'ok'])])
FIX
from itertools import groupby
statuses = ['ok', 'fail', 'ok']
ordered = sorted(statuses)
print([(key, list(group)) for key, group in groupby(ordered)])

Sort first when you need one group per key, and consider whether sorting is acceptable for your data.

Logic & loopsFrom Build Small Data Pipelines with itertools

Trying to reuse an exhausted pipeline

The second list is empty because the iterator has already been consumed.

See the fix
COMMON MISTAKE
stream = chain([1], [2])
print(list(stream))
print(list(stream))
FIX
stream = chain([1], [2])
values = list(stream)
print(values)
print(values)

Recreate an iterator for another pass or store the results if repeated access is required.

Functions & classesFrom Understand Recursion and the Call Stack

Recursing on the unchanged input

The input never shrinks, so calls end in RecursionError.

See the fix
COMMON MISTAKE
def length(items):
    if not items: return 0
    return 1 + length(items)
FIX
def length(items):
    if not items: return 0
    return 1 + length(items[1:])

Each recursive call must move closer to the empty-list base case.

Functions & classesFrom Understand Recursion and the Call Stack

Ignoring the child's return value

Numbers inside nested lists never contribute to the parent total.

See the fix
COMMON MISTAKE
if isinstance(item, list):
    sum_nested(item)
FIX
if isinstance(item, list):
    total += sum_nested(item)

The parent must use the value returned by its child call.

Functions & classesFrom Understand Recursion and the Call Stack

Sharing an accumulator between calls

A second call starts from the first call's answer and nested lists are not handled.

See the fix
COMMON MISTAKE
total = 0
def sum_nested(items):
    global total
    for item in items: total += item
    return total
FIX
def sum_nested(items):
    total = 0
    for item in items:
        total += sum_nested(item) if isinstance(item, list) else item
    return total

A local total belongs to each call and is combined by returned values.

Functions & classesFrom Read Structured Data with match and case

Using a capture where a literal is required

message captures any value, so this branch matches everything.

See the fix
COMMON MISTAKE
match event_type:
    case message:
        return 'message'
FIX
match event_type:
    case 'message':
        return 'message'

Quoted text is a literal pattern; an unqualified bare name captures.

Functions & classesFrom Read Structured Data with match and case

Placing a broad branch too early

The origin is handled by the first two-item pattern and never reaches its specific branch.

See the fix
COMMON MISTAKE
match point:
    case (x, y): return 'point'
    case (0, 0): return 'origin'
FIX
match point:
    case (0, 0): return 'origin'
    case (x, y): return 'point'

Order specific shapes before general ones.

Functions & classesFrom Read Structured Data with match and case

Assuming a shape also means valid content

Whitespace-only messages are treated as meaningful content.

See the fix
COMMON MISTAKE
case {'type': 'message', 'text': str(text)}:
    return 'Message: ' + text
FIX
case {'type': 'message', 'text': str(text)} if text.strip():
    return 'Message: ' + text.strip()

A guard expresses conditions that a shape alone does not capture.

Files & setupFrom Replace Debug Prints with Python Logging

Expecting INFO to appear under a WARNING threshold

The INFO record is filtered out before it reaches a handler.

See the fix
COMMON MISTAKE
logger.setLevel(logging.WARNING)
logger.info('started')
FIX
logger.setLevel(logging.INFO)
logger.info('started')

Choose a threshold at or below the messages you need.

Files & setupFrom Replace Debug Prints with Python Logging

Adding a new handler to a shared logger on every call

Repeated setup can make the same event appear multiple times.

See the fix
COMMON MISTAKE
logger = logging.getLogger('app')
logger.addHandler(logging.StreamHandler(stream))
FIX
logger = logging.Logger('one-call')
logger.addHandler(logging.StreamHandler(stream))

Use a fresh logger for a self-contained task or manage and remove shared handlers explicitly.

Files & setupFrom Replace Debug Prints with Python Logging

Logging secrets while debugging

The token may be written to logs and retained outside the program.

See the fix
COMMON MISTAKE
logger.info('token=%s', api_token)
FIX
logger.info('Authentication request started')

Log the event and context needed for diagnosis without credentials or personal details.

Functions & classesFrom Start Concurrent I/O with asyncio

Calling a coroutine without driving it

The result is a coroutine object rather than '1: ADA', and it may be reported as never awaited.

See the fix
COMMON MISTAKE
result = fetch_label(1, 'ada')
print(result)
FIX
result = asyncio.run(fetch_label(1, 'ada'))
print(result)

At a top-level script, asyncio.run executes the coroutine. Inside async code, use await instead.

Functions & classesFrom Start Concurrent I/O with asyncio

Awaiting independent waits one at a time

Independent waits are serialized, so their delays add up.

See the fix
COMMON MISTAKE
results = []
for item in jobs:
    results.append(await fetch(item))
FIX
results = await asyncio.gather(*(fetch(item) for item in jobs))

gather schedules independent awaitables together; result order remains aligned with jobs.

Functions & classesFrom Start Concurrent I/O with asyncio

Starting another event loop inside async code

asyncio.run raises RuntimeError when an event loop is already running.

See the fix
COMMON MISTAKE
async def main():
    return asyncio.run(fetch_label(1, 'ada'))
FIX
async def main():
    return await fetch_label(1, 'ada')

Use await inside a coroutine and reserve asyncio.run for a synchronous top-level script.

Functions & classesFrom Start Concurrent I/O with asyncio

Gathering jobs that depend on each other

The profile job cannot receive a token that has not been returned yet.

See the fix
COMMON MISTAKE
await asyncio.gather(fetch_token(), fetch_profile(token))
FIX
token = await fetch_token()
profile = await fetch_profile(token)

Start independent waits together; keep a real producer-consumer dependency in sequence.

ProjectsFrom Project: Analyze Application Logs

Splitting every pipe

A message containing a pipe creates four parts and unpacking raises ValueError.

See the fix
COMMON MISTAKE
parts = line.split("|")
timestamp, level, message = parts
FIX
parts = [part.strip() for part in line.split("|", 2)]
if len(parts) != 3 or not all(parts):
    raise ValueError("Malformed log row")
timestamp, level, message = parts

Limit the split to two separators and validate the three fields before unpacking.

ProjectsFrom Project: Analyze Application Logs

Treating malformed rows as events

A row without a separator raises IndexError; an empty level can create a misleading category.

See the fix
COMMON MISTAKE
level = raw_line.split("|")[1].strip()
FIX
parts = [part.strip() for part in raw_line.split("|", 2)]
if len(parts) != 3 or not all(parts):
    skipped += 1
    continue

Validate the record shape and required fields before using their positions.

ProjectsFrom Project: Flashcard Quiz

Letting zip hide an unanswered card

With three cards and two responses, the third card is never graded.

See the fix
COMMON MISTAKE
for card, response in zip(cards, responses):
    grade(card, response)
FIX
if len(cards) != len(responses):
    raise ValueError("One response is required for each card")
for card, response in zip(cards, responses):
    ...

Check the one-to-one contract before pairing the lists.

CollectionsFrom Shallow vs Deep Copy in Python

Mistaking an outer copy for full isolation

The input's nested score changes too.

See the fix
COMMON MISTAKE
updated = report.copy()
updated['scores'][0]['points'] = 10
FIX
from copy import deepcopy
updated = deepcopy(report)
updated['scores'][0]['points'] = 10

A shallow copy retains the original scores list and row dictionaries.

CollectionsFrom Shallow vs Deep Copy in Python

Using equality to prove independence

Equal contents do not show whether nested objects are shared.

See the fix
COMMON MISTAKE
assert copied == original  # therefore no shared objects
FIX
assert copied == original
assert copied is not original
assert copied['scores'][0] is not original['scores'][0]

Inspect identity at the mutable level whose independence matters.

Functions & classesFrom Create a Custom Python Exception for Validation

Raising the generic conversion error only

Nonnumeric text raises ValueError, while zero and negatives pass through.

See the fix
COMMON MISTAKE
return int(text)
FIX
try:
    units = int(text)
except ValueError as exc:
    raise QuantityError(f'Invalid units: {text!r}') from exc
if units <= 0:
    raise QuantityError(f'Invalid units: {text!r}')
return units

Conversion and the positive-quantity rule both belong to the function's contract.

Functions & classesFrom Create a Custom Python Exception for Validation

Swallowing every exception

A bug inside calculate is silently converted into a valid-looking zero.

See the fix
COMMON MISTAKE
try:
    return calculate(text)
except:
    return 0
FIX
except ValueError as exc:
    raise QuantityError(f'Invalid units: {text!r}') from exc

Catch the expected failure and keep a diagnosable exception path.

Functions & classesFrom Read Python Tracebacks and Fix the Failing Layer

Catching a bug instead of fixing its line

Passing a dictionary to int is hidden and an invalid port looks successful.

See the fix
COMMON MISTAKE
try:
    return int(config)
except TypeError:
    return 0
FIX
text = config['port']
number = int(text)

Use the traceback to repair the wrong expression before handling expected input failures.

Functions & classesFrom Read Python Tracebacks and Fix the Failing Layer

Defaulting a missing required key

A missing port becomes a misleading out-of-range value instead of identifying the absent key.

See the fix
COMMON MISTAKE
text = config.get('port', 0)
FIX
text = config['port']

Required fields should fail clearly when absent; KeyError identifies the missing key.

CollectionsFrom Build Queues and Stacks with deque

Using stack removal for a queue

Linus is served before Ada.

See the fix
COMMON MISTAKE
waiting = ['Ada', 'Grace', 'Linus']
next_name = waiting.pop()
FIX
from collections import deque
waiting = deque(['Ada', 'Grace', 'Linus'])
next_name = waiting.popleft()

A FIFO queue removes from the opposite end from append.

CollectionsFrom Build Queues and Stacks with deque

Shifting a list for every request

Each front removal shifts the remaining list items.

See the fix
COMMON MISTAKE
while waiting:
    served.append(waiting.pop(0))
FIX
waiting = deque(waiting)
while waiting:
    served.append(waiting.popleft())

deque provides efficient operations at both ends.

CollectionsFrom Schedule Jobs with heapq

Unorderable payloads on a priority tie

The second push raises TypeError when dictionaries are compared.

See the fix
COMMON MISTAKE
heapq.heappush(heap, (1, {'id': 'A'}))
heapq.heappush(heap, (1, {'id': 'B'}))
FIX
heapq.heappush(heap, (1, 0, {'id': 'A'}))
heapq.heappush(heap, (1, 1, {'id': 'B'}))

A unique counter is a comparable tie breaker.

CollectionsFrom Find Insertion Points with bisect

Using index for an absent item

ValueError is raised because 25 is not present.

See the fix
COMMON MISTAKE
position = scores.index(25)
FIX
position = bisect_left(scores, 25)

bisect_left finds an insertion position whether or not the value exists.

CollectionsFrom Find Insertion Points with bisect

Searching unsorted values

The returned position cannot guarantee a sorted result.

See the fix
COMMON MISTAKE
scores = [20, 10, 40]
position = bisect_left(scores, 15)
FIX
scores = sorted(scores)
position = bisect_left(scores, 15)

Establish sorted order before binary search; decide separately whether sorting a copy fits your contract.

Functions & classesFrom Memoize Pure Functions with lru_cache

Caching changing external state

Later calls may reuse an old price.

See the fix
COMMON MISTAKE
@lru_cache(maxsize=None)
def current_price(item):
    return read_live_price(item)
FIX
def current_price(item):
    return read_live_price(item)

A changing data source needs an explicit refresh or cache invalidation policy.

Functions & classesFrom Memoize Pure Functions with lru_cache

Passing an unhashable list as a key

TypeError: unhashable type: 'list'.

See the fix
COMMON MISTAKE
@lru_cache(maxsize=None)
def total(values):
    return sum(values)
print(total([1, 2]))
FIX
@lru_cache(maxsize=None)
def total(values):
    return sum(values)
print(total((1, 2)))

A tuple of integers is hashable and can be used as a cache key.

Files & setupFrom Convert Aware Datetimes with zoneinfo

Treating a naive timestamp as UTC

Python interprets the naive time using the host's local timezone.

See the fix
COMMON MISTAKE
value = datetime.fromisoformat('2026-01-15T12:00:00')
print(value.astimezone(ZoneInfo('America/New_York')))
FIX
value = datetime.fromisoformat('2026-01-15T12:00:00+00:00')
print(value.astimezone(ZoneInfo('America/New_York')))

Provide an explicit offset before converting a moment.

Functions & classesFrom Choose Composition Over Unnecessary Inheritance

Inheriting from a dependency

Changing to another sender requires changing the class hierarchy.

See the fix
COMMON MISTAKE
class NotificationService(EmailSender):
    def notify(self, message):
        return self.send(message)
FIX
class NotificationService:
    def __init__(self, sender): self.sender = sender
    def notify(self, message): return self.sender.send(message)

The service uses a sender rather than being one.

Functions & classesFrom Use Properties to Keep Python Objects Valid

Calling the setter recursively

Assigning the property inside its own setter repeatedly calls that setter.

See the fix
COMMON MISTAKE
@celsius.setter
def celsius(self, value):
    self.celsius = value
FIX
@celsius.setter
def celsius(self, value):
    self._celsius = value

Store into a distinct backing attribute after validation.

Functions & classesFrom Use Properties to Keep Python Objects Valid

Bypassing validation in the constructor

An invalid object can be created even if later writes are checked.

See the fix
COMMON MISTAKE
def __init__(self, celsius):
    self._celsius = celsius
FIX
def __init__(self, celsius):
    self.celsius = celsius

Route initial assignment through the public property.

Functions & classesFrom Use Properties to Keep Python Objects Valid

Mutating before checking

The object holds a bad value after the exception.

See the fix
COMMON MISTAKE
self._celsius = value
if value < -273.15: raise ValueError('too cold')
FIX
if value < -273.15: raise ValueError('too cold')
self._celsius = value

Complete validation before committing state.

Files & setupFrom Read Environment Configuration Safely

Using bool on textual flags

The nonempty string 'false' evaluates to True.

See the fix
COMMON MISTAKE
debug = bool(env.get('APP_DEBUG', 'false'))
FIX
text = env.get('APP_DEBUG', 'false').strip().lower()
if text in ('true', '1'):
    debug = True
elif text in ('false', '0'):
    debug = False
else:
    raise ValueError('APP_DEBUG must be true or false')

Recognize both true and false spellings, and reject every other supplied value.

Files & setupFrom Read Environment Configuration Safely

Falling back on malformed supplied configuration

A typo silently changes where the program listens.

See the fix
COMMON MISTAKE
try: port = int(env['APP_PORT'])
except ValueError: port = 8000
FIX
try: port = int(env.get('APP_PORT','8000'))
except ValueError as error: raise ValueError('invalid APP_PORT') from error

Defaults are for absence, not malformed input.

Files & setupFrom Read Environment Configuration Safely

Printing a secret during troubleshooting

The token may be copied into terminal logs or support records.

See the fix
COMMON MISTAKE
print('API_TOKEN', env.get('API_TOKEN'))
FIX
print('API_TOKEN configured:', bool(env.get('API_TOKEN')))

Report presence or a safe status instead of a credential value.

Files & setupFrom Read a JSON API with urllib

Making an unchecked network call

The operation can wait indefinitely or return an unexpected shape.

See the fix
COMMON MISTAKE
with urlopen(url) as response: data = json.load(response)
FIX
with urlopen(request, timeout=5) as response:
    data = json.load(response)
if not isinstance(data, list):
    raise ValueError('expected list')

The timeout bounds waiting; the shape check rejects a non-list response. Validate individual items before using them, as in the next fix.

Files & setupFrom Read a JSON API with urllib

Assuming valid JSON always has a title

A dictionary, numeric item, or missing title can cause an unrelated error or misleading result.

See the fix
COMMON MISTAKE
return [item['title'] for item in data]
FIX
if not isinstance(data, list):
    raise ValueError('expected list')
titles = []
for item in data:
    if not isinstance(item, dict):
        raise ValueError('expected object')
    title = item.get('title')
    if not isinstance(title, str) or not title.strip():
        raise ValueError('expected nonblank title')
    titles.append(title.strip())
return titles

Validate the list and each object before extracting a stripped, nonblank title.

Functions & classesFrom Make Objects Work with repr, ==, and len

Returning False for every foreign object

The other operand cannot try its reflected equality behavior.

See the fix
COMMON MISTAKE
if not isinstance(other, Book): return False
FIX
if not isinstance(other, Book): return NotImplemented

NotImplemented is a dispatch signal, not an exception.

Functions & classesFrom Make Objects Work with repr, ==, and len

Using a human label as value equality

A formatting change can silently change equality semantics.

See the fix
COMMON MISTAKE
return repr(self) == repr(other)
FIX
return (self.title, self.pages) == (other.title, other.pages)

Compare the fields that define the value, not its display text.

Functions & classesFrom Test Dependencies with unittest.mock

Checking output but ignoring a wrong argument

A function could call the fake with a different ID and still receive its fixed return value.

See the fix
COMMON MISTAKE
assert load_user_label(7, fake) == '7: Ada'
FIX
assert load_user_label(7, fake) == '7: Ada'
fake.assert_called_once_with(7)

Verify a contractual interaction when a fixed mock response would hide a wrong call.

Files & setupFrom Build a Small CLI with argparse

Reading ambient argv in a browser task

The result depends on the process running the example, not the supplied exercise input.

See the fix
COMMON MISTAKE
options = parser.parse_args()
FIX
options = parser.parse_args(argv)

Inject tokens for deterministic practice and testing.

Files & setupFrom Build a Small CLI with argparse

Assuming integer conversion enforces positivity

Zero and negative integers parse successfully.

See the fix
COMMON MISTAKE
parser.add_argument('--limit', type=int)
# assume 0 is rejected
FIX
if options.limit < 1: raise ValueError('limit must be positive')

Validate business rules after syntax conversion.

Files & setupFrom Build a Small CLI with argparse

Ignoring unknown options

A misspelled flag can disappear without warning.

See the fix
COMMON MISTAKE
options, extra = parser.parse_known_args(argv)
# silently discard extra
FIX
options = parser.parse_args(argv)

For this command, unknown arguments should be errors.

Files & setupFrom Process JSON Lines One Record at a Time

Trusting every decoded value

Lists, numbers, and records with missing or empty event values fail unpredictably.

See the fix
COMMON MISTAKE
counts[record['event']] += 1
FIX
if not isinstance(record, dict) or not isinstance(record.get('event'), str) or not record['event']:
    raise ValueError('invalid event')

Validate the application schema after decoding syntax.

Files & setupFrom Build Query Strings with urllib.parse

Appending a second question mark

The new text is added inside an existing query and the space is not encoded.

See the fix
COMMON MISTAKE
url = 'https://example.test/x?page=1'
print(url + '?q=red fox')
FIX
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
url = 'https://example.test/x?page=1'
p = urlsplit(url)
q = parse_qsl(p.query, keep_blank_values=True) + [('q', 'red fox')]
print(urlunsplit((p.scheme, p.netloc, p.path, urlencode(q), p.fragment)))

Build one query from pairs, then reassemble the components once.

Files & setupFrom Build Query Strings with urllib.parse

Losing repeated keys in a dictionary

Only the final tag value remains in the dictionary.

See the fix
COMMON MISTAKE
params = dict([('tag', 'a'), ('tag', 'b')])
FIX
from urllib.parse import urlencode
params = [('tag', 'a'), ('tag', 'b')]
print(urlencode(params))

Keep the ordered pair list when multiple values for a key are allowed.

Strings & textFrom Escape Text for HTML Output

Undoing the escape before output

Character references become angle brackets again before insertion.

See the fix
COMMON MISTAKE
from html import unescape
print(unescape('&lt;b&gt;Hi&lt;/b&gt;'))
FIX
from html import escape
label = '<b>Hi</b>'
print(escape(label, quote=True))

Keep the text escaped for HTML output; unescape is for decoding input when that is actually required.

Files & setupFrom Read INI Settings with configparser

Treating retrieved text as an integer

A string and integer cannot be added; the operation raises TypeError.

See the fix
COMMON MISTAKE
from configparser import ConfigParser
p=ConfigParser(); p.read_string('[job]\nretries=3\n')
print(p.get('job','retries') + 1)
FIX
from configparser import ConfigParser
p=ConfigParser(); p.read_string('[job]\nretries=3\n')
print(p.getint('job','retries') + 1)

Use getint and validate the resulting number.

Files & setupFrom Read INI Settings with configparser

Assuming every percent is interpolation syntax

A lone percent sign raises InterpolationSyntaxError with default interpolation.

See the fix
COMMON MISTAKE
from configparser import ConfigParser
p=ConfigParser(); p.read_string('[job]\nstatus=100% ready\n'); print(p.get('job','status'))
FIX
from configparser import ConfigParser
p=ConfigParser(interpolation=None); p.read_string('[job]\nstatus=100% ready\n'); print(p.get('job','status'))

Turn off interpolation when configuration values should be literal.

Files & setupFrom Create ZIP Archives in Memory

Using text methods on bytes without decoding

TypeError because bytes.split expects a bytes separator, not str.

See the fix
COMMON MISTAKE
from io import BytesIO
from zipfile import ZipFile
b=BytesIO()
with ZipFile(b,'w') as z: z.writestr('x.txt', b'one two')
with ZipFile(BytesIO(b.getvalue())) as z: print(z.read('x.txt').split(' '))
FIX
from io import BytesIO
from zipfile import ZipFile
b=BytesIO()
with ZipFile(b,'w') as z: z.writestr('x.txt', b'one two')
with ZipFile(BytesIO(b.getvalue())) as z: print(z.read('x.txt').decode('utf-8').split(' '))

Decode when converting stored bytes back to text.

Files & setupFrom Create ZIP Archives in Memory

Allowing path-like names in a simple archive

The entry name describes a relative parent path that is inappropriate for this basename-only exercise.

See the fix
COMMON MISTAKE
archive.writestr('../notes.txt', b'hello')
FIX
name='notes.txt'
assert '/' not in name and '\\' not in name
archive.writestr(name, b'hello')

Validate names before archive creation; extraction also needs a separate policy.

ProjectsFrom Project: CSV Import Validator with Row Errors

Failing the whole import at the first bad cell

Later valid rows are never inspected.

See the fix
COMMON MISTAKE
qty = int(row['qty'])  # uncaught
FIX
try: qty = int(raw_qty)
except ValueError:
    errors.append((reader.line_num, 'invalid qty'))
    continue

Catch ordinary row-level validation errors under the chosen partial-success contract.

ProjectsFrom Project: Verify a ZIP Archive Against a Manifest

Checking names but not content

A changed b.txt is reported as healthy.

See the fix
COMMON MISTAKE
changed = []  # names match, so files match
FIX
changed = [name for name in common if hashlib.sha256(archive.read(name)).hexdigest() != manifest[name]]

Hash member bytes for every expected present file.

ProjectsFrom Project: Verify a ZIP Archive Against a Manifest

Extracting an untrusted archive to inspect it

Inspection writes every member to disk and may expose path or resource risks.

See the fix
COMMON MISTAKE
archive.extractall(output_dir)
FIX
data = archive.read(name)
digest = hashlib.sha256(data).hexdigest()

This small fixture verifier only needs bytes, not extraction.

ProjectsFrom Project: Offline Web Crawler on a Fixture Graph

Never recording scheduled URLs

A home/about cycle can revisit pages until the limit is exhausted.

See the fix
COMMON MISTAKE
queue.extend(all_links)
FIX
if target in pages and target not in queued:
    queued.add(target)
    queue.append(target)

Mark a fixture URL when it is scheduled, not after another path finds it.

ProjectsFrom Project: Offline Web Crawler on a Fixture Graph

Claiming the browser fixture is a live crawl

Arbitrary network requests are outside this deterministic browser-console exercise.

See the fix
COMMON MISTAKE
html = urllib.request.urlopen(url).read()
FIX
html = pages[url]

Keep the algorithm offline; teach real network crawling separately with operational safeguards.

Files & setupFrom Query SQLite Safely with Parameters

Interpolating an untrusted value

Quotes break the query and SQL-looking values can alter its meaning.

See the fix
COMMON MISTAKE
query = f"SELECT name FROM items WHERE category = '{category}'"
FIX
connection.execute('SELECT name FROM items WHERE category = ?', (category,))

Bind values; do not manufacture SQL syntax from external text.

Files & setupFrom Query SQLite Safely with Parameters

Assuming rows arrive in insertion order

A later query plan can return a different order.

See the fix
COMMON MISTAKE
SELECT name FROM items WHERE category = ?
FIX
SELECT name FROM items WHERE category = ? ORDER BY name

Specify the order required by the interface.

Logic & loopsFrom Make Random Simulations Reproducible

Changing the shared generator

Other code's results depend on this function's call order.

See the fix
COMMON MISTAKE
random.seed(seed)
value = random.random()
FIX
rng = random.Random(seed)
value = rng.random()

Keep simulation state local.

Logic & loopsFrom Make Random Simulations Reproducible

Recreating the generator for every trial

Every trial repeats the first value.

See the fix
COMMON MISTAKE
for _ in range(count):
    value = random.Random(seed).random()
FIX
rng = random.Random(seed)
for _ in range(count):
    value = rng.random()

Create once, then advance that instance.

Files & setupFrom Replace a File Safely with a Temporary File

Leaving a failed temporary file behind

An exception can leave an orphaned name and an open file.

See the fix
COMMON MISTAKE
temp = NamedTemporaryFile(delete=False); temp.write(data); os.replace(temp.name, target)
FIX
close the temporary file; use finally to unlink its name if it still exists

Failure cleanup is part of the replacement pattern.

Logic & loopsFrom Calculate Exact Ratios with Fraction

Dividing by invalid weights

Zero total weight divides by zero; negative weights make confusing shares.

See the fix
COMMON MISTAKE
left = total * left_weight / (left_weight + right_weight)
FIX
if type(left_weight) is not int or left_weight <= 0: raise ValueError('positive weight required')

Validate both ratio weights before calculation.

Functions & classesFrom Define Structural Interfaces with typing.Protocol

Using a presence check as complete validation

A noncallable render attribute may still pass the presence check.

See the fix
COMMON MISTAKE
if isinstance(item, Renderable): return item.render()
FIX
method = getattr(item, 'render', None)
if not callable(method): raise TypeError('render required')

Validate the runtime behavior this function relies on.

Functions & classesFrom Parse Python Literals Without eval

Using eval as a parser

The input can execute code instead of supplying data.

See the fix
COMMON MISTAKE
values = eval(user_text)
FIX
values = ast.literal_eval(user_text)

Parse literal syntax with a limited parser, then validate it.

Functions & classesFrom Parse Python Literals Without eval

Accepting every parsed literal

Tuples, booleans, huge values, or nested data slip through.

See the fix
COMMON MISTAKE
return ast.literal_eval(text)
FIX
if type(values) is not list or any(type(x) is not int for x in values):
    raise ValueError('integer list required')

Apply the application's exact shape and range contract.

Strings & textFrom Build and Parse Email Messages Offline

Putting unvalidated multiline input in a header

A supplied newline violates this function's header contract.

See the fix
COMMON MISTAKE
message['Subject'] = user_subject
FIX
if '\n' in user_subject or '\r' in user_subject:
    raise ValueError('single-line subject required')

Validate header shape before assignment.

Files & setupFrom Compress Text and Bytes with Python gzip

The visible failure

Passing str directly to gzip.compress raises TypeError because the compressor expects bytes.

See the fix
COMMON MISTAKE
gzip.compress('hello')
FIX
gzip.compress('hello'.encode('utf-8'), mtime=0)

Use gzip.compress(text.encode('utf-8'), mtime=0) to make a reproducible sample payload, and gzip.decompress(payload).decode('utf-8') to recover the text. The mtime setting affects gzip header metadata; it is not a security feature and does not promise equal compression across every Python or zlib version.

Files & setupFrom Compress Text and Bytes with Python gzip

Missing the edge condition

A byte string that is not a gzip member raises an error during decompression, so blindly decoding arbitrary input is unsafe.

See the fix
COMMON MISTAKE
gzip.decompress(b'not gzip').decode('utf-8')
FIX
validate and limit the compressed input before consuming it

Compression can drastically expand during decompression for specially prepared input. The tiny classroom example does not enforce a production memory budget. Handle invalid data and size limits where the bytes enter a real service, and never treat gzip as encryption.

Files & setupFrom Pack Binary Records with Python struct

The visible failure

Unpacking a one-byte buffer with a three-byte format raises struct.error.

See the fix
COMMON MISTAKE
struct.unpack('>HB', b'\x03')
FIX
if len(packet) == struct.calcsize('>HB'): struct.unpack('>HB', packet)

Use struct.pack('>HB', identifier, status) to build the packet and struct.unpack('>HB', packet) to recover a tuple. Range-check integers against the format before packing if user input is involved. An unsigned short supports 0 through 65535 and an unsigned byte supports 0 through 255.

Files & setupFrom Pack Binary Records with Python struct

Missing the edge condition

The verifier attempts to decode a truncated record and deliberately catches struct.error.

See the fix
COMMON MISTAKE
struct.pack('HB', identifier, status)
FIX
struct.pack('>HB', identifier, status)

A binary protocol must define length, byte order, version and validation explicitly. Native struct formats may add platform-dependent padding, so avoid an implicit native format for data exchanged between systems. The example is a local three-byte protocol, not a network framing implementation.

Files & setupFrom Test Text Streams with Python StringIO

The visible failure

Reading a StringIO stream advances its cursor.

See the fix
COMMON MISTAKE
stream.read(); stream.read()
FIX
stream.read(); stream.seek(0); stream.read()

Implement count_lines(stream) as a single pass over the provided text stream. StringIO makes it easy to test that behavior without filesystem setup. To repeat the call on the same object, explicitly rewind with seek(0), then compare the count again.

Files & setupFrom Test Text Streams with Python StringIO

Missing the edge condition

An empty in-memory stream has zero lines.

See the fix
COMMON MISTAKE
len(stream.read().split('\n'))
FIX
sum(1 for _ in stream)

StringIO is a text stream, not a byte stream; use BytesIO when code expects bytes. It is also a test aid, not evidence that a function handles disk permissions or very large files. Those are separate integration concerns.

Files & setupFrom Read TOML Configuration with Python tomllib

The visible failure

Indexing a missing nested key raises KeyError, while blindly using get with a default can hide a typo in a required setting.

See the fix
COMMON MISTAKE
config['service']['retry']
FIX
validate config['service']['retries'] after parsing

Use tomllib.loads for a text string and tomllib.load for a binary file object. After parsing, validate required tables and values explicitly; raise ValueError with a useful message when the contract is not met. tomllib does not write TOML, so do not assume a dumps method exists.

Files & setupFrom Read TOML Configuration with Python tomllib

Missing the edge condition

An invalid TOML document raises TOMLDecodeError before any application validation runs.

See the fix
COMMON MISTAKE
return config.get('service', {}).get('retries', 3)
FIX
raise ValueError when required retries is missing or invalid

Configuration may contain secrets; avoid printing complete parsed dictionaries in production logs. The classroom string contains public sample settings only. Keep the distinction between syntax errors, missing settings and invalid values clear to a caller who must fix a configuration file.

Files & setupFrom Find Files with Python glob and fnmatch

The visible failure

fnmatch may normalize case according to the host operating system.

See the fix
COMMON MISTAKE
fnmatch.fnmatch(name, '*.py')
FIX
fnmatch.fnmatchcase(name, '*.py')

Use fnmatch.fnmatchcase(name, '*.py') for a case-sensitive in-memory filter, and sorted(...) for deterministic results. When searching actual directories, use pathlib.Path.glob or glob.glob but validate the search root and do not assume a pattern by itself enforces access controls.

Files & setupFrom Find Files with Python glob and fnmatch

Missing the edge condition

The final run includes a nested name like src/main.py.

See the fix
COMMON MISTAKE
return [name for name in names if name.endswith('.py')]
FIX
return sorted(name for name in names if '/' not in name and fnmatch.fnmatchcase(name, '*.py'))

Shell-style glob patterns are not regular expressions. Asterisks and question marks have different syntax from re, and a pattern is not a security boundary. Define whether matching is case-sensitive and whether paths or base names are allowed before using results to delete or publish files.

Logic & loopsFrom Benchmark Tiny Python Functions with timeit

The visible failure

Calling time.perf_counter once before and once after a single tiny operation gives a number, but its precision does not make it trustworthy.

See the fix
COMMON MISTAKE
time.perf_counter() around one tiny call
FIX
timeit.repeat(callable, number=many_calls, repeat=several_runs)

Write per_call(total_seconds, number) to divide a measured duration by a positive integer call count. The function is simple by design: it keeps the unit conversion honest, while the demonstration shows how timeit supplies each total. A benchmark needs a stated interpreter, machine and input size before drawing conclusions.

Logic & loopsFrom Benchmark Tiny Python Functions with timeit

Missing the edge condition

Zero calls cannot yield a meaningful per-call number and would divide by zero.

See the fix
COMMON MISTAKE
return total_seconds
FIX
return total_seconds / number after validating number > 0

Benchmark results are observations, not language guarantees. The local demo does not prove which implementation is faster on the user's machine or production workload. Start by confirming both functions return the same value; then measure realistic inputs and report enough context to interpret the numbers.

Logic & loopsFrom Find a Shortest Unweighted Path with BFS

The visible failure

Using list.pop() treats the frontier as a LIFO stack, producing depth-first traversal.

See the fix
COMMON MISTAKE
frontier.pop()
FIX
frontier.popleft() using collections.deque

Initialize deque with the path containing start. Pop paths from the left, append neighbor paths on the right, and mark a node visited when enqueuing it. Return the first path ending at goal. For this small teaching graph, storing whole paths is readable; larger graphs can store predecessor pointers instead.

Logic & loopsFrom Find a Shortest Unweighted Path with BFS

Missing the edge condition

If start equals goal, the shortest path is the one-node list.

See the fix
COMMON MISTAKE
return [] when no route exists
FIX
return None for unreachable goal

BFS minimizes the number of edges only when edges have equal cost. It does not know that one route is geographically faster or cheaper than another. For large graphs, a queue of complete path copies consumes extra memory; reconstruct a path from parent pointers when scalability matters.

Logic & loopsFrom Detect Directed Graph Cycles with DFS

The visible failure

A single visited set is insufficient for directed cycle detection.

See the fix
COMMON MISTAKE
if neighbor in visited: return True
FIX
if neighbor in active: return True; if neighbor in done: continue

Maintain active and done sets. Entering a node adds it to active; after all descendants return without a cycle, move it to done. On a neighbor already active return True; on a done neighbor continue. Start DFS from every graph key to cover disconnected components; recursive visits also reach referenced neighbors.

Logic & loopsFrom Detect Directed Graph Cycles with DFS

Missing the edge condition

A self-loop such as X to X is a cycle; an empty graph is not.

See the fix
COMMON MISTAKE
search only graph.keys() without referenced nodes
FIX
include nodes appearing in adjacency values too

This detector is for directed edges. For an undirected graph, the immediate edge back to a parent needs special treatment. Document graph representation and depth limits before using this recursive version on untrusted large graphs.

Logic & loopsFrom Solve Coin Change with Dynamic Programming

The visible failure

Greedy is correct for some coin systems but not for arbitrary denominations.

See the fix
COMMON MISTAKE
take the largest affordable coin repeatedly
FIX
compute dp[amount] from every reachable dp[amount - coin]

Initialize dp[0] to zero and all other entries to an unreachable marker. For each amount, consider only positive coins not larger than that amount; if dp[amount - coin] is reachable, update dp[amount]. Return None when the target stays unreachable. Reject negative targets and nonpositive denominations before building the table.

Logic & loopsFrom Solve Coin Change with Dynamic Programming

Missing the edge condition

Target zero requires zero coins.

See the fix
COMMON MISTAKE
return infinity for unreachable amount
FIX
return None for unreachable amount

This algorithm minimizes coin count, not monetary value or weight; denominations define the available moves. Production financial calculations have other constraints and should not infer optimality from a greedy shortcut without proving it for the specific coin system.

Logic & loopsFrom Compute a Moving Average with a Sliding Window

The visible failure

A range ending at len(values) - size omits the final valid starting position.

See the fix
COMMON MISTAKE
range(len(values) - size)
FIX
initialize first window, then update through index len(values)-1

Initialize a sum for the first size values, append its average, then for each later index add values[index] and subtract values[index - size]. Append one average after each update. Reject nonpositive window sizes, and return an empty list when size exceeds the data length.

Logic & loopsFrom Compute a Moving Average with a Sliding Window

Missing the edge condition

A window size equal to the data length yields one average.

See the fix
COMMON MISTAKE
average a partial final window
FIX
return [] if size > len(values)

Floating-point averages can have tiny representation differences; tests should use appropriate tolerance for nonexact decimals. The lesson uses small integer inputs whose output is exactly representable. For streaming data too large to store, the same running-sum idea can be paired with a bounded deque.

Logic & loopsFrom Count Connected Components with Union-Find

The visible failure

Starting with one component per node and subtracting the number of edges gives the wrong answer for a cycle.

See the fix
COMMON MISTAKE
len(nodes) - len(edges)
FIX
decrement only when union joins two different roots

Initialize parent[node] = node for every declared node, including isolated ones. A find function follows parent links and compresses paths. Union roots for each undirected edge, decrementing the component count only when the roots differ. In this compact demo an unknown edge endpoint raises KeyError from the parent lookup; validate endpoints explicitly if the caller needs a friendlier error.

Logic & loopsFrom Count Connected Components with Union-Find

Missing the edge condition

An empty graph has zero components, and four isolated nodes have four.

See the fix
COMMON MISTAKE
initialize parents from edge endpoints only
FIX
initialize parents for every declared node

This small implementation uses path compression and is adequate for the exercise. For very large dynamic graphs, union by rank or size can further control tree height. Union-find answers connectivity under added edges; removing edges requires another strategy.

Logic & loopsFrom Answer Range Sum Queries with Prefix Sums

The visible failure

If the prefix array starts at the first input value instead of a leading zero, empty ranges and ranges beginning at zero become awkward and off-by-one errors appear.

See the fix
COMMON MISTAKE
prefix = [values[0]]
FIX
prefix = [0]; append(prefix[-1] + value) for each value

Build prefix by starting with [0] and appending the last total plus each next value. A query validates 0 <= start <= end <= len(values), then subtracts two stored totals. This precomputation costs one pass; each later query uses constant-time indexing and subtraction.

Logic & loopsFrom Answer Range Sum Queries with Prefix Sums

Missing the edge condition

An empty range where start equals end returns zero, and the full range uses the final prefix entry.

See the fix
COMMON MISTAKE
prefix[end+1] - prefix[start]
FIX
prefix[end] - prefix[start] for [start,end)

Prefix sums are appropriate when source values stay unchanged between queries. After a value changes, later prefix totals become stale and must be rebuilt or maintained with another data structure. Keep the half-open interval convention visible in function names and tests.

Functions & classesFrom Validate Dataclasses with __post_init__

Assuming annotations enforce values

Range(8, 2) constructs even though the bounds are reversed.

See the fix
COMMON MISTAKE
@dataclass
class Range:
    low: int
    high: int
FIX
def __post_init__(self):
    if self.low > self.high:
        raise ValueError('low must not exceed high')

Annotations document intended types; explicit logic enforces the relationship.

Functions & classesFrom Manage Async Tasks with TaskGroup and Cancellation

Reading results before the group finishes

InvalidStateError may occur because the child has not completed.

See the fix
COMMON MISTAKE
task = group.create_task(job())
print(task.result())
FIX
async with asyncio.TaskGroup() as group:
    task = group.create_task(job())
print(task.result())

Wait for context exit before reading the result.

Functions & classesFrom Manage Async Tasks with TaskGroup and Cancellation

Suppressing cancellation in a child

The group may observe misleading child state and cleanup semantics.

See the fix
COMMON MISTAKE
try:
    await work()
except asyncio.CancelledError:
    return None
FIX
try:
    await work()
finally:
    release_resource()

Clean up in finally; generally let cancellation propagate.

CollectionsFrom Compare Groups with Python Set Operations

Reversing set difference

The function returns people only on the right.

See the fix
COMMON MISTAKE
right_set - left_set  # left-only
FIX
left_set - right_set  # left-only

Difference keeps values from the first operand that are absent from the second.

Files & setupFrom Read Nested JSON with Explicit Missing-Field Rules

Chaining get without checking types

A present list or null can still raise AttributeError or mask a malformed shape.

See the fix
COMMON MISTAKE
record.get('user', {}).get('address', {}).get('city')
FIX
if not isinstance(address, dict):
    raise ValueError('address must be an object')

Verify each present container's type explicitly.

Files & setupFrom Project: Build a Gradebook Report

Sorting only by average

Tie order depends on input order rather than a documented rule.

See the fix
COMMON MISTAKE
sorted(rows, key=lambda row: -row['average'])
FIX
sorted(rows, key=lambda row: (-row['average'], row['student']))

The second key gives deterministic ranking.

Strings & textFrom Parse Quoted Command Input with shlex

Treating tokens as executable shell input

User text can become shell syntax.

See the fix
COMMON MISTAKE
subprocess.run(line, shell=True)
FIX
Dispatch known commands in Python

Validate token[0] against an allowlist and never concatenate it into a shell command.

Files & setupFrom Validate JSON from HTTP Responses in Python

Decoding every response

An HTML error page raises JSONDecodeError or a JSON error is treated as data.

See the fix
COMMON MISTAKE
payload = json.loads(body) before checking status
FIX
if status != 200: raise ValueError('unexpected status')

Transport status and payload syntax are separate checks.

ProjectsFrom Build an Offline Python API Client Simulator

Caching an error response

A temporary 503 becomes a permanent cached error.

See the fix
COMMON MISTAKE
self.cache[path] = self.transport(path) before status validation
FIX
cache only a validated 200 user record

Cache insertion belongs after transport and schema checks.

CollectionsFrom Pandas GroupBy Multiple Columns with Named Aggregations

Losing missing-key rows

Rows with a missing region or product are absent from the report.

See the fix
COMMON MISTAKE
sales.groupby(['region', 'product']).amount.sum()
FIX
sales.groupby(['region', 'product'], dropna=False).amount.sum()

Choose whether missing-key rows belong in reconciliation totals.

CollectionsFrom Pandas Merge on Multiple Columns Without Duplicate Matches

Trusting a left join without checks

Duplicate lookup pairs can multiply orders and unmatched rows have NaN prices.

See the fix
COMMON MISTAKE
orders.merge(pricebook, how='left', on=['sku','region'])
FIX
orders.merge(pricebook, how='left', on=['sku','region'], validate='many_to_one', indicator=True)

State cardinality and inspect left_only rows.

CollectionsFrom Pandas Drop Duplicates by Column and Keep the Latest Row

Keeping the last input row without sorting

A newer row appearing earlier can be discarded.

See the fix
COMMON MISTAKE
frame.drop_duplicates(subset=['email'], keep='last')
FIX
frame.sort_values('_parsed_time', kind='stable').drop_duplicates(subset=['email'], keep='last')

Define chronological order before choosing the last row.

Functions & classesFrom Python subprocess.run Command: Capture Output Safely

Interpolating data into shell syntax

User-controlled text can change what the shell executes.

See the fix
COMMON MISTAKE
subprocess.run(f'python child.py {name}', shell=True)
FIX
subprocess.run([sys.executable, 'child.py', name])

Keep untrusted text in an argv element and avoid an unnecessary shell.

Files & setupFrom FastAPI request body: validate JSON with a Pydantic model

Using an untyped dict as the entire contract

The API does not document the expected fields and limits as clearly.

See the fix
COMMON MISTAKE
def create_item(item: dict): ...
FIX
Declare a BaseModel with required typed fields and constraints.

FastAPI can then validate the body and publish its schema in OpenAPI docs.

Files & setupFrom FastAPI request body: validate JSON with a Pydantic model

Assuming every integer-like value is acceptable

A boolean can be treated as quantity one in handwritten checks.

See the fix
COMMON MISTAKE
isinstance(True, int)
FIX
State the chosen coercion policy; use strict constraints when needed.

Type coercion and business rules deserve an explicit decision.

Files & setupFrom Python cProfile example: find slow functions and read pstats

Reading cumulative time as self time

A wrapper function is blamed for work in its subcalls.

See the fix
COMMON MISTAKE
cumtime == time spent only in this function's own lines
FIX
Compare cumtime with tottime and inspect callees.

A profiler report separates inclusive and exclusive time.

Files & setupFrom Python cProfile example: find slow functions and read pstats

Profiling an unrepresentative input

An algorithm that scales poorly appears harmless.

See the fix
COMMON MISTAKE
profile a list of three items and extrapolate
FIX
Profile representative inputs and compare under the same conditions.

Input size and shape affect where time is spent.

Files & setupFrom openpyxl read Excel in Python: rows, headers, and values

Treating formula cache as calculation

Formula cells may be None or stale.

See the fix
COMMON MISTAKE
load_workbook(path, data_only=True)  # computes formulas
FIX
Use cached values only after a spreadsheet application has recalculated and saved the workbook.

openpyxl reads a workbook; it is not a formula engine.

CollectionsFrom NumPy reshape: change array dimensions without shape errors

Reversing rows and columns

The number supplied becomes the row count instead of the requested column count.

See the fix
COMMON MISTAKE
array.reshape(columns, -1)
FIX
array.reshape(-1, columns)

The last dimension is the number of entries per row.

CollectionsFrom NumPy reshape: change array dimensions without shape errors

Expecting reshape to resize data

ValueError because the requested shape requires eight elements.

See the fix
COMMON MISTAKE
np.arange(6).reshape(4, 2)
FIX
np.arange(6).reshape(2, 3)

Reshape preserves the total element count.

Files & setupFrom Matplotlib savefig: Export a PNG Without Clipped Labels

Assuming tight crop preserves exact pixels

The resulting dimensions differ from the assumed canvas.

See the fix
COMMON MISTAKE
fig.savefig(path, dpi=200, bbox_inches='tight')  # always 1200x600
FIX
Inspect the saved dimensions if exact pixels matter.

Bounding-box cropping changes the output canvas.

Functions & classesFrom FastAPI Query Parameters: Validate skip and limit

Leaving page size unbounded

A caller can request an excessive response size.

See the fix
COMMON MISTAKE
def list_items(limit: int = 10): ...
FIX
limit: int = Query(default=10, ge=1, le=50)

A bounded HTTP parameter makes the contract explicit and documented.

Files & setupFrom pytest tmp_path: test file-writing code without touching real files

Writing into the repository

Tests depend on the working directory and can overwrite a real file.

See the fix
COMMON MISTAKE
Path('report.txt').write_text(data)
FIX
Pass tmp_path to the writer and build tmp_path / 'report.txt'.

A destination parameter makes the file side effect testable and isolated.

Files & setupFrom pytest tmp_path: test file-writing code without touching real files

Checking only a return value

The test may pass without confirming the saved content.

See the fix
COMMON MISTAKE
assert save_report(tmp_path, lines).name == 'report.txt'
FIX
Read the returned path with encoding='utf-8' and compare the text.

The disk content is the behavior this test promises.

Functions & classesFrom Dataclass to dict in Python: asdict, nested objects, and copy behavior

Assuming JSON compatibility

JSON encoding raises TypeError for unsupported values.

See the fix
COMMON MISTAKE
json.dumps(asdict(record_with_datetime))
FIX
Convert special values explicitly before json.dumps, or supply a deliberate serializer.

A Python dict is not automatically valid JSON data.

Functions & classesFrom jsonschema validate Python data: required fields and readable errors

Treating validation as coercion

The string fails the integer rule.

See the fix
COMMON MISTAKE
validator.validate({'age': '20'})  # expects 20
FIX
Parse or reject the string before validation according to your input contract.

jsonschema checks the instance; it does not transform a value into another type.

Files & setupFrom Python CSV to JSON: convert rows with DictReader and json.dumps

Silently accepting a ragged row

Extra cells may appear under a None key; missing cells may become null.

See the fix
COMMON MISTAKE
rows = list(csv.DictReader(stream)); json.dumps(rows)
FIX
Check headers and each row before serializing.

A predictable output schema depends on validating the CSV structure.

CollectionsFrom NumPy boolean indexing: filter arrays with masks and avoid copy surprises

Trying to mutate a selected copy

The original values array remains unchanged.

See the fix
COMMON MISTAKE
selected = values[mask]; selected[0] = 0
FIX
Assign through values[mask] = 0 when the source should change.

Boolean selection returns a separate array.