Python Data Types and Structures
Pick the right container, avoid the type catastrophes
Strings, numbers, booleans, lists, tuples, dicts and sets are the bricks of every analysis. Knowing when each is right — and how they convert — prevents most data-quality bugs.
What you will learn
- Distinguish mutable vs immutable and ordered vs hashable structures
- Convert between types safely
- Choose list, tuple, dict or set for the job
What is this?
Python's core types each encode a trade-off. Lists keep order and allow duplicates. Tuples are immutable lists — safe as dict keys. Dicts map keys to values with O(1) lookup. Sets hold unique, unordered elements for membership and dedupe. Strings and numbers have precision and coercion rules that bite when data crosses type boundaries.
Why do you need it?
- Half of 'data quality' issues are type mismatches: string '100' vs int 100.
- The right container makes code clearer and faster (dict lookup vs list scan).
- Overflow, rounding and datetime parsing failures are type problems in disguise.
- Understanding mutability prevents the classic 'shared list' bug that corrupts analyses.
Mental model
Choosing containers in a warehouse
Python data structures
You don't store loose screws in huge bins — you pick bins, labels and shelves by how you'll retrieve them. Lists/dicts/sets are different shelving: ordered racks, labeled drawers, and a checklist of unique items.
How it works
- ints and floats carry precision rules (float has rounding; int is exact).
- str is immutable and sequence-like; it converts via int(), float(), str().
- list is ordered, mutable, allows duplicates; dict is keyed, unordered (insertion-ordered in modern Python), fast lookup.
- tuple is immutable and hashable — usable as a dict key; set stores unique values and supports &, |, .isdisjoint().
- bool is a subclass of int (False==0) — a classic surprise in summaries.
Choosing the container.
Examples
Containers in one glance
row = { "city": "austin", "sales": 120, "tags": ("tx", "growth") }cities = ["austin", "houston", "austin"]unique = set(cities) # {"austin", "houston"}pairs = [(c, row["sales"]) for c in unique]print(pairs)print(row["city"].upper(), int("100") + 5)A dict holds the record, a list keeps order, a set dedupes, a tuple stays hashable — and the string '100' converts to an int for math.
In production
- A billing audit dedupes invoices with a set before summing, avoiding double-pay counts.
- A retention team keys a dict by customer_id for O(1) lookups instead of scanning a list.
- A field-reconciliation script compares dicts of old vs new schema to catch renamed columns.
Best practices
- Normalize types at the boundary: parse '100' once into an int, never string-compare numbers.
- Use tuples for fixed records and frozenset when you need a hashable set.
- Prefer .get(key, default) over bare subscripting to survive missing keys.
- Confirm boolean assumptions — count True/False explicitly rather than relying on truthiness.
Common mistakes
- Adding an int and a numeric string and wondering why '100' + 5 fails.
- Using a list as a dict key and hitting 'unhashable type: list'.
- Mutating a default argument list and carrying state between calls.
- Comparing floats with == after arithmetic that can't be represented exactly.
Debugging tips
- Print type(x) before the failing operation — the bug usually lives at the boundary.
- A 'TypeError: unhashable' means you need a tuple, not a list.
- Float drift: round() before comparisons or use math.isclose.
- When string comparison silently passes (or fails), inspect trailing whitespace and case.
Interview checkpoint
Quick quiz
Which structure gives O(1) keyed lookup?
A list can be used as a dict key.