✎ Edit content·DAY 016 · POST 5 OF 5 · Common Mistakes

Python Data Structures

Python · 12 slides
DAY 016 · POST 5 OF 5
(REMINDER)
DAY 016
5 Container Traps
@saurav_dnj_24github.com/SauravDnj · linkedin.com/in/sauravdnj
1 / 12

Theme

Palette

Download

4K — sharpest, slowest
🎬 Video options
Preparing preview…
Live preview · loops the “none” effect
All rendering runs in your browser. No server, no cost, no upload. MP4/WebM = full motion + effects · GIF = lightweight loop · PNG/PDF = static for the Instagram & LinkedIn carousel.

Caption (tap to copy)

📖 Deep dive (full written explanation)

The slides stay clean and scannable. Here's the in-depth explanation behind each one — great for the blog version, show notes, or studying the topic properly.
Slide 1 · 5 Container Traps

Post five is the defensive close: the five container traps that recur across real Python codebases. The cover makes the case that these aren't exotic edge cases — they're a small, finite set of mistakes that everyone hits, often repeatedly, until they've been named and recognized.

The value of naming them is pattern recognition. Once you've seen the mutable-default trap or the reference-versus-copy surprise explained, you spot it instantly in code review, which is exactly the leverage this post is after.

Slide 2 · 1. Mutable default arguments

The mutable default argument is the most infamous Python gotcha. A default value is evaluated once, when the function is defined, not on each call. So def f(items=[]) creates a single list that every call shares, and it silently accumulates state between invocations — a bug that's baffling until you know the cause.

The standard fix is the None sentinel: default the parameter to None, then create a fresh list inside the function when the argument wasn't provided. This guarantees each call starts clean.

Slide 3 · The default-arg trap & fix

This snippet demonstrates the trap and the fix concretely. The bad version's bucket persists across calls, so the second call sees the first call's item still present — output [1] then [1, 2] instead of [1] then [2]. The good version checks for None and builds a fresh list each time, restoring the expected behavior.

Seeing the leaked state in the printed output makes the abstract 'defaults evaluate once' rule click. The None pattern is worth adopting as an unconditional habit for any mutable default.

Slide 4 · 2. Assignment is not a copy

Assignment binding versus copying is the second trap, and it underlies many 'why did this change?' bugs. b = a does not duplicate the list; it makes b a second name for the exact same object. Mutating through either name affects the one shared list, which surprises people coming from value-copy languages.

The fix is an explicit copy: a.copy() or list(a) for a shallow copy of the top level, and copy.deepcopy when the structure nests and you need the inner objects duplicated too.

Slide 5 · Reference vs copy

This snippet shows the reference trap and its remedy plainly. After b = a, appending through b changes what a sees, because there's only one list. Making c with a.copy() produces an independent object, so appending to c leaves a untouched.

The distinction between names and objects is foundational to Python's model. Internalizing it explains not just this trap but also the nested-list surprise from the previous post and why immutable types sidestep the whole issue.

Slide 6 · Two names, one object

The flow diagram visualizes the core of the reference trap: two names, a and b, both pointing at one underlying list object. The single object at the end of the arrows is the whole lesson — there is one list, and the names are just labels attached to it.

This picture is the antidote to the intuition that assignment copies. Keeping it in mind makes you reach for .copy() automatically whenever you need two independent collections rather than two views of one.

Slide 7 · 3. Mutating while iterating

Mutating a list while iterating over it is the third trap. The loop tracks position by index, so removing an element shifts everything after it down by one, causing the loop to skip the next element — or to raise if you're modifying the structure underneath the iterator. The bug is intermittent in a way that hides it during casual testing.

The robust fixes are to iterate over a copy with items[:], or, better, to build a new filtered list with a comprehension and reassign — never editing the collection you're walking.

Slide 8 · Don't delete while looping

This snippet contrasts the buggy delete-while-looping approach with the clean comprehension fix. The commented-out version would skip elements as the list shrinks beneath the iterator. The comprehension builds an entirely new list of the items to keep, then reassigns the name — leaving the iteration and the construction completely separate.

The comprehension approach is not just safer but clearer: it states the keep-condition directly rather than expressing it as a sequence of removals, which is easier to read and reason about.

Slide 9 · 4. Unhashable keys

The fourth trap is using an unhashable object as a key or set member. Only immutable, hashable objects qualify, because the hash must stay stable for the table to find the entry again. Lists and dicts are mutable and therefore unhashable, which is why they can't be keys.

The practical fixes: convert a list key to a tuple, and use frozenset when you need set-like values inside another set. This trap connects directly to the hash-table mechanics from post three — the rule isn't arbitrary, it's what keeps the table correct.

Slide 10 · 5. Trusting order that isn't there

The fifth trap is relying on order where the container makes no such promise. Dicts have preserved insertion order since 3.7, but sets are genuinely unordered — the iteration order can vary by run, version, or platform, and code that depends on a set printing a certain way is silently fragile.

The related error is trying to index a dict by position; dicts are accessed by key, and treating them positionally is a category mistake. The discipline is to honor each container's contract: trust dict insertion order if you must, but never trust set order.

Slide 11 · The five, in one glance

The final tips compress all five traps into a glanceable checklist: default mutable arguments to None, remember that assignment shares rather than copies, never mutate the list you're iterating, keep keys immutable, and don't trust set order. Each line maps to one trap, so the list doubles as a recall device.

These five cover the overwhelming majority of container-related bugs in practice. Keeping them in working memory turns most of these mistakes from things you debug into things you simply don't write.

Slide 12 · Save this. Follow for Day 17.

The CTA closes both the post and the day. With the containers defined, motivated, mechanically understood, practiced, and de-trapped, you have a complete working foundation in Python's data structures. The series continues building from here, one focused topic at a time.

🎨 AI image prompt (matches this theme + palette)

Paste into Midjourney, DALL·E, Ideogram, etc. to generate an on-brand image, then upload it on the Edit content page. The prompt updates automatically with the selected theme + palette.