You copied a list with b = a, changed b, and the "original" a changed too.
Or a function with a default like def add(row, rows=[]) keeps returning records
from earlier calls, as if it remembers the past. No error appears anywhere — the
program runs happily and produces wrong data.
Hits when a script starts keeping backup copies of data, or when a helper function grows an optional list/dict argument. Classic moment: "I saved a copy of the clean records before filtering, but the copy got filtered too." Common in exercise code, interview problems, and real cleaning scripts alike. Because nothing crashes, it is often discovered days later from wrong totals. Linked module: 0.3 (Python that AI actually uses).
Assignment in Python never copies an object — it copies a reference, so b = a makes
two names for ONE list. Any change through either name shows through both; this is
aliasing. The mutable default is the same disease at the definition line: Python
builds the default list ONCE, when the function is defined, and every call without
that argument shares that single list, so appends pile up across calls. Confirm with
identity, not appearance: print(id(a), id(b)) — equal ids means one object with two
names. a is b returning True says the same. For the default: call the function
twice with no list argument; if the second result contains the first call's items,
you have the shared default.
For aliasing: make a real copy at the assignment — b = list(a) or b = a.copy().
Note these copy one level only; a list of dicts still shares the inner dicts, and
copy.deepcopy(a) is the full-depth copy when you truly need one. For the mutable
default: use the None pattern —
def add(row, rows=None): then inside, if rows is None: rows = [].
This builds a fresh list per call. Re-run the identity check afterwards: a is b
must now be False, and two bare calls must return independent results.
Say the rule aloud when you type =: names point at objects; assignment shares,
.copy() copies. Never write a list or dict literal as a default argument — the
None pattern is the professional reflex, and code reviewers reject rows=[] on
sight. When a value changes "by itself", reach for id() or is first: identity
evidence beats staring at code. Comprehensions help too — building a new list at
each step leaves nothing shared to mutate.
Q: "What does this print?" followed by the b = a; b.append(...) snippet, or "what
is wrong with def f(x, items=[])?" — both are top-ten Python interview questions in
Indian service and product companies. Strong answer: state that assignment copies
references not objects, predict the shared-change output confidently, explain the
one-time creation of default values, and give both fixes (list(a)/copy(), and the
None pattern) with the one-level limit of shallow copies. Bonus credit for is and
id() as the confirming tools.