dict internals & ordering
Overview
A dict maps hashable keys to values using a hash table, giving O(1) average-time lookups (instant retrieval regardless of size), inserts, and deletes. Keys must be immutable (str, int, tuple) — lists and dicts cannot be keys. Since Python 3.7+ dicts preserve insertion order. Create one with braces or dict(): user = {'name': 'sam', 'role': 'admin', 'active': True} Access a value by key — direct access raises KeyError if the key is missing, while .get() returns a default instead: user['name'] # 'sam' user['email'] # KeyError! user.get('email', 'n/a') # 'n/a' — safe fallback .keys(), .values(), and .items() return live view objects — they reflect changes to the dict without copying: for k, v in user.items(): print(k, v) Mutation methods: user['email'] = 'sam@co.io' # add or overwrite a key del user['active'] # remove a key (KeyError if missing) user.pop('role', None) # remove and return value, or default user.setdefault('plan', 'free') # insert 'plan' only if absent user.update({'active': False}) # merge another dict in place Dict unpacking (**) merges dicts into a new one without mutating either source: merged = {**defaults, **overrides} Common pattern — grouping items by a key: groups = {} for item in items: groups.setdefault(item['team'], []).append(item) Think of a dict as a filing cabinet: each drawer has a unique label (key) and holds one document (value). You open a drawer by its label in constant time — you never search drawer-by-drawer.
Dicts are the backbone of Python data handling — JSON payloads are dicts, FastAPI response models serialize to dicts, config files parse into dicts, and ORM row objects expose dict-like access. Choosing .get() over direct access prevents crashes on optional fields, and understanding view objects avoids wasteful copies in hot loops.
Where used: FastAPI response models, JSON API payloads, config dicts and env var mappings, ORM query results
Why learn this
- Reading and building JSON payloads in FastAPI endpoints — every request body and response is a dict under the hood
- Grouping database rows by category or counting occurrences without external libraries
- Safely accessing optional config keys with .get() instead of crashing on KeyError
Code walkthrough
defaults = {'debug': False, 'region': 'us-east-1'}
overrides = {'debug': True, 'timeout': 30}
config = {**defaults, **overrides}
print(config)
print(config.get('retries', 3))
Focus: Dict unpacking merges both dicts into a new one — overrides wins on 'debug', and .get() safely returns a default for the missing 'retries' key.
Aha moment
settings = {'debug': False}
alias = settings
alias['debug'] = True
print(settings['debug'])
Prediction: After changing alias['debug'] to True, what does settings['debug'] print?
Common guess: False
alias = settings does not copy the dict — both names point to the same object. Mutating through alias is visible through settings. To get an independent copy use settings.copy().
Common mistakes
- Using direct access on optional keys: Direct access raises a `KeyError` if the key is missing. Instead, use `.get('timeout', 30)` to safely supply a default value.
- Iterating and mutating at the same time: This raises a `RuntimeError`. Instead, iterate over a static snapshot by wrapping the keys in a list, like `list(d.keys())`.
- Expecting dict.update() to return the merged dict: This method mutates the dictionary in place and evaluates to `None`. To create a new merged dictionary, use unpacking syntax instead: `{**base, **extra}`.
Glossary
- hash table
- A data structure that stores information in a way that allows for extremely fast retrieval using a unique key. Python dicts use one internally, which is why `d['key']` is instant regardless of how large the dict is. Example: `{'id': 1}`
- serialize
- The process of converting complex data structures into a format that can be easily stored or transmitted, like turning a Python dictionary into a JSON string. Example: `json.dumps({'id': 1})`
Recall questions
- What is the average time complexity for lookups, inserts, and deletes in a Python dictionary?
- What method can you use to access a dictionary value safely without raising a KeyError if it is missing?
Understanding checks
What happens when this code runs?
None
The `.update()` method mutates the dictionary in place and returns `None`. It does not return the updated dictionary.
Why does attempting to use a list as a dictionary key result in a `TypeError`?
Lists are mutable, meaning their contents can change, which makes them unhashable. Dictionary keys must be hashable and immutable (like tuples, strings, or integers).
Dictionaries use a hash table internally, which requires the key's hash value to never change over its lifetime.
Practice tasks
Group users by role and get config safely
Update the `get_config` function to safely return the fallback value instead of raising an error. Also update the `group_by_role` function to group users using `.setdefault()` instead of the current `if/else` logic.
Challenge
Merge environment configs with precedence
Write merge_env(base, env_overrides, cli_overrides) that returns a NEW config dict with precedence: cli_overrides > env_overrides > base. Do not mutate any input dict. Then demonstrate that the base dict is unchanged after the merge.
Continue learning
Previous: Mutable vs immutable types
Previous: Slicing