Python notes for dictionaries, sets, hashing, operations, patterns, and quick checks.
Name (key) → Phone number (value). You look up a name, instantly get the number. No scanning through all entries — jump straight to it.
Key insight: pairs of (key, value). Every key is unique. Values can repeat.
A list of unique attendees. You care only about membership — is this person invited? No duplicates. No order matters.
Key insight: uniqueness only. No values, no order, just existence.
| Question | Use | Why |
|---|---|---|
| "Does X exist?" | Set | O(1) membership check |
| "What is the value for X?" | Dict | Key → value lookup |
| "How many times does X appear?" | Dict | Frequency counting |
| "Find duplicates / unique items" | Set | Auto-deduplication |
| "Group items by category" | Dict | Key = category, val = list |
| "Common items between two groups" | Set | intersection() |
The core rule: if you need to associate a value with a key → dict. If you only need to know presence/absence → set.
A collection of key-value pairs where each key is unique. Implemented internally as a hash table.
| Property | dict |
|---|---|
| Keys unique? | Yes — duplicate key overwrites |
| Values unique? | No — duplicates allowed |
| Ordered? | Yes (insertion order, Python 3.7+) |
| Mutable? | Yes — add, update, delete freely |
| Key type requirement | Must be hashable (immutable) |
An unordered collection of unique elements. Internally, it's a hash table that only stores keys (no values).
| Property | set |
|---|---|
| Elements unique? | Yes — automatically |
| Ordered? | No — no guaranteed order |
| Mutable? | Yes (use frozenset for immutable) |
| Element type | Must be hashable |
Conceptual name used in CS/Java. In Python, dict IS the HashMap. Same O(1) operations, same hash table internals.
Conceptual name used in CS/Java. In Python, set IS the HashSet. A hash table storing only keys.
Java/C++ say HashMap/HashSet. Python says dict/set. They're the same concept — tell interviewers you know both.
A hash function converts any key into an integer index. That index tells Python exactly which bucket to put/find the value — no searching needed.
Collision: two keys land in the same bucket. Python handles this via open addressing — it finds the next free slot.
| Type | Hashable? | Can be a dict key / set element? |
|---|---|---|
| int, float, str, tuple | Yes | Yes |
| bool, None | Yes | Yes |
| list | No | No — TypeError |
| dict | No | No — TypeError |
| set | No | No (use frozenset) |
| Feature | dict | set | list | tuple |
|---|---|---|---|---|
| Stores | key-value pairs | unique elements | ordered elements | ordered elements |
| Ordered? | Yes (3.7+) | No | Yes | Yes |
| Duplicates? | Values yes, keys no | No | Yes | Yes |
| Mutable? | Yes | Yes | Yes | No |
| Lookup | O(1) by key | O(1) membership | O(n) search | O(n) search |
| Index access? | By key only | No | Yes (d[i]) | Yes (d[i]) |
| Use when | key→value map | unique items | ordered sequence | immutable sequence |
| Memory | High | Medium-high | Low | Lowest |
Common mistake: {} creates an empty dict, NOT a set. Always use set() for an empty set.
| Method | What it does | Returns |
|---|---|---|
| d.get(k, default) | Get value, no KeyError if missing | value or default |
| d.keys() | All keys | dict_keys view |
| d.values() | All values | dict_values view |
| d.items() | All (key, value) pairs | dict_items view |
| d.update(other) | Merge another dict in | None |
| d.pop(k, default) | Remove key, return value | value or default |
| d.setdefault(k, v) | Get key; insert v if missing | existing or new value |
| k in d | Membership test O(1) | bool |
| Method | What it does | Alt syntax |
|---|---|---|
| s.add(x) | Add element | — |
| s.remove(x) | Remove — KeyError if missing | — |
| s.discard(x) | Remove — no error if missing | — |
| s.union(t) | All elements from both | s | t |
| s.intersection(t) | Elements in both | s & t |
| s.difference(t) | In s but not in t | s - t |
| s.issubset(t) | All of s in t? | s <= t |
| x in s | Membership O(1) | — |
| Operation | dict avg | dict worst | set avg | set worst |
|---|---|---|---|---|
| Insert / Add | O(1) | O(n) | O(1) | O(n) |
| Delete / Remove | O(1) | O(n) | O(1) | O(n) |
| Lookup / Membership | O(1) | O(n) | O(1) | O(n) |
| Iteration | O(n) | O(n) | O(n) | O(n) |
| union / intersection | — | — | O(m+n) | O(m+n) |
| Space | O(n) | O(n) | O(n) | O(n) |
Worst case O(n) happens on many hash collisions (all keys in one bucket). In practice with Python's dict, this is extremely rare.
O(1) average comes from the hash function mapping directly to the bucket. No iteration needed. This is why x in my_set beats x in my_list for large n.
Given nums and target, return indices of two numbers that add to target.
Time: O(n) — Space: O(n)
Time: O(n) — Space: O(1) since at most 26 chars
Time: O(n) — Space: O(min(n, alphabet))
| Mistake | Problem | Fix |
|---|---|---|
| Using list as dict key | TypeError: unhashable | Convert to tuple first |
| d["k"] on missing key | KeyError | d.get("k", 0) |
| {} for empty set | Creates dict, not set | set() |
| Mutating dict during iteration | RuntimeError | Iterate over list(d.keys()) |
| x in d.values() | O(n) not O(1) | Restructure — key lookup instead |
| Assuming set is ordered | Wrong output | Use list + sort if order matters |
Prefer defaultdict(int) over d.get(k, 0) — cleaner for frequency counting.
Use Counter for frequency — it has built-in .most_common(k).
Convert a list to set for O(1) membership: lookup = set(my_list) then x in lookup.
dict consumes ~8x more memory than a list of same size. Avoid for memory-critical applications.
In interviews: always mention O(1) average lookup as the key reason for choosing dict/set. Interviewers love hearing this explicitly.
| CS / Java / C++ | Python equivalent |
|---|---|
| HashMap | dict |
| HashSet | set |
| LinkedHashMap | dict (ordered since 3.7) |
| TreeMap (sorted) | sortedcontainers.SortedDict |
| Map.getOrDefault(k, v) | d.get(k, v) |
| map.containsKey(k) | k in d |
| set.contains(x) | x in s |