Collections Best Practices
Practical rules for choosing and using Python's built-in containers without performance traps or readability debt.
How to Use This List
- Apply during code review on data-heavy modules.
- Revisit when profiling shows unexpected O(n) behavior.
- Pair with type hints (
dict[str, int]) for clearer contracts. - Use as onboarding checklist for data pipeline code.
A - Selection
- Pick
dictfor keyed lookup,setfor membership,listfor ordered sequences. Document non-obvious choices. - Use
dequefor FIFO/LIFO queues instead oflist.pop(0). O(1) vs O(n) at front. - Use
heapqfor priority scheduling;bisectfor sorted insertions. Notsorted()on every insert. - Reach for pandas 2.2+ or Polars 1.x at tabular scale, not nested dicts of lists. Column ops and joins.
- Prefer
Counteranddefaultdictover manual key-exists boilerplate. Fewer branches, clearer intent.
B - Correctness
- Never use mutable lists or dicts as dict keys or set elements. Use tuple, frozenset, or frozen dataclass.
- Shallow-copy nested structures with
copy.deepcopywhen independence required.dict.copy()shares inner lists. - Treat set iteration order as unspecified. Use
sorted(s)for stable display only. - Validate uniqueness before inverting dict
{v: k}. Colliding values overwrite silently. - Use
getortry/except KeyErrorintentionally.d[k]when key must exist;getwhen optional.
C - Performance and Memory
- Build
setonce for repeated membership in loops. Avoid O(n)in listin hot paths. - Use generator expressions feeding
sum/any/maxfor large streams. Skip materializing million-item lists. - Consider
array.arrayor bytes for compact numeric/binary buffers. Not list of ints for wire protocols. - Profile before swapping list for exotic structure. Small n favors simplicity.
- Avoid repeated
keys()snapshot unless mutating during iteration.list(d)when deleting while looping.
D - Readability
- Use dict/set comprehensions for transforms with optional filters. Clearer than manual loops.
- Name types in hints:
dict[str, list[Event]], not baredict. Self-documenting structures. - Use
NamedTupleor dataclass instead of positional tuple indexing for records.row[3]obscures meaning. - Prefer
|merge for immutable config overlays (3.9+).base | overridesreads left-to-right. - Order-preserving dedupe:
list(dict.fromkeys(items)). Idiomatic one-liner.
E - Serialization and APIs
- JSON APIs use dict/list/str/int/float/bool/None only at boundaries. Convert sets/tuples to lists for JSON.
- Do not rely on dict order for semantic equality across systems. Compare as mappings, not key lists.
- Freeze config dicts at startup when passing through layers.
MappingProxyTypeor typed settings object. - Document empty container semantics -
[]vsNone. Callers should not guess missing vs empty. - Use Pydantic 2 models at HTTP boundaries; plain dicts inside hot domain logic when appropriate.
FAQs
When is OrderedDict still justified?
move_to_end LRU patterns. Regular dict handles insertion order otherwise.
list or tuple in API return?
Tuple for fixed small bundles; list when returning variable-length homogeneous collection.
Should I always use slots?
__slots__ for millions of small fixed-field objects. Premature for typical app records.
Counter vs defaultdict(int)?
Counter adds multiset operators and most_common - prefer for frequency work.
How avoid defaultdict serialization surprises?
Convert dict(defaultdict) before JSON - factory not serialized.
Are comprehensions always pythonic?
Yes for map/filter transforms. Switch to loop for side effects or complex branching.
dict vs dataclass for records?
dataclass/Pydantic for domain objects with behavior; dict for JSON passthrough or dynamic keys.
When use ChainMap?
Layered config search without copying - env over defaults. Flatten when persisting.
Is frozenset worth it?
When a set of tags must be a dict key or cache entry - otherwise plain set.
Biggest collections mistake?
Linear search on list inside loop - fix with set/dict index built once.
Related
- Choosing the Right Data Structure - decision guide
- Data Structures Basics - intro examples
- Immutability & Hashability - hash rules
- collections Module - specialized types
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.