Dictionaries: operations and use cases

RoadmapsPython

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'] = 'x@co.io'` — add a new key or overwrite an existing one - `del user['active']` — remove a key; raises `KeyError` if the key is missing - `user.pop('role', None)` — remove AND return the value, or the default if absent; without a default, a missing key raises `KeyError` - `user.setdefault('plan', 'free')` — return the existing value, inserting the default only if the key is absent - `user.update({'active': False})` — merge another dict in place; returns `None`, not the merged dict Dict unpacking (**) merges dicts into a new one without mutating either source: merged = {**defaults, **overrides} Copying: `user.copy()` and `dict(user)` are shallow copies — the outer dict is new, but nested values (lists, inner dicts) are still shared with the original, exactly like list slicing. Mutating `copy['tags'].append(x)` changes the original too; `copy.deepcopy()` (covered in shallow-vs-deep-copy) is the fix when values are mutable. 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

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

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

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: Primitive types: int, float, bool, str

Previous: Mutable vs immutable objects

Previous: Lists: indexing, slicing, methods

Next: Time Complexity of Collection Operations

Next: Dictionary and set comprehensions

Return to Python Roadmap