Skip to main content
Python Basics2026-07-31 · 10 min read

Python Dictionaries: The 2026 Deep-Dive

Dictionaries are the workhorse Python data structure. If lists are the array in your toolkit, dicts are the hashmap — and 80% of real Python code uses them without thinking. This tutorial covers the parts most tutorials skip: safe access patterns, the 3.9+ merge operator, defaultdict and Counter, dict comprehensions, and the three cases where a dict is the wrong tool.

The mental model in one sentence

A Python dict is an ordered hashmap where keys are unique and hashable, values are anything. Since Python 3.7, insertion order is preserved (was implementation-defined before, guaranteed since then). dict and OrderedDict are essentially the same today unless you need move_to_end.

▶ PYTHONeditable · runs in your browser

Safe access: never use d[key] for optional values

The single biggest source of Python KeyError in production is d[key] on a key that might not exist. Use .get():

▶ PYTHONeditable · runs in your browser

.get() never raises KeyError. Reserve d[key] for the case where a missing key IS a bug — then let it crash loudly.

.setdefault() — the one-liner "get or insert"

When you need to insert a default AND return it:

▶ PYTHONeditable · runs in your browser

Equivalent to the four-line if key not in d: d[key] = []; d[key].append(x) — but atomic and readable.

The | merge operator (3.9+)

Before Python 3.9, merging dicts required {**a, **b} or dict(a, **b). The 3.9 release introduced | for merge and |= for in-place merge:

▶ PYTHONeditable · runs in your browser

Right side wins on conflicts. Same semantics as {**defaults, **user_config} — but | reads like a real language feature, not a shell hack.

defaultdict — for aggregation loops

collections.defaultdict auto-creates missing entries. Cleaner than .setdefault() when the factory is a call:

▶ PYTHONeditable · runs in your browser

Avoids the if key not in d: d[key] = 0 boilerplate. Fifty times in a codebase adds up.

Counter — the specialised subclass

When you're counting, collections.Counter is defaultdict(int) + convenience methods:

▶ PYTHONeditable · runs in your browser

One of Python's most underused stdlib gems. If you're writing a for-loop with += 1, reach for Counter first.

Dict comprehensions

Same shape as list comprehensions, but {k: v for ...}:

▶ PYTHONeditable · runs in your browser

Inverting a dict is the canonical use — one line vs the four-line for-loop equivalent.

Iteration patterns

▶ PYTHONeditable · runs in your browser

The .items() form is the one you'll use 90% of the time. dict.keys() is rarely needed explicitly — iterating over dict gives you keys already.

Membership check

Use in, never .keys():

▶ PYTHONeditable · runs in your browser

in on a dict checks membership in .keys() by default. .keys() is a view object; explicit is worse than implicit here.

Nested dicts — the chain.get trick

One pain point: d["a"]["b"]["c"] crashes if any level is missing.

▶ PYTHONeditable · runs in your browser

For 2-3 levels, chained .get({}) is fine. For 4+ or dynamic path, use glom or write a helper.

When a dict is the WRONG tool

1. Fixed schema → use a @dataclass

If you always have the same keys with known types, dataclasses are self-documenting AND type-checked:

▶ PYTHONeditable · runs in your browser

Rule: if the keys are STATIC, use a dataclass. Dicts are for STRING → VALUE mappings where the set of strings is dynamic (config, cache, aggregation).

2. Ordered pairs → use a list of tuples

▶ PYTHONeditable · runs in your browser

Dicts DO preserve insertion order since 3.7 — but a list of tuples signals "order matters" to the next reader.

3. Enum-like membership → use set

▶ PYTHONeditable · runs in your browser

Sets have the same O(1) in performance as dicts — cheaper mental model when you don't need values.

Common gotchas

1. Mutable defaults: d.setdefault(key, []) returns the SAME list each time — mutating it mutates the stored value. That's the point, but expect surprise:

```python

d = {}

xs = d.setdefault("a", [])

xs.append(1)

print(d) # → {'a': [1]}

```

2. `dict.fromkeys` with mutable value: dict.fromkeys(["a","b"], []) gives you two keys pointing to the SAME list. Don't do it.

3. Unhashable keys: lists / dicts can't be dict keys. Use tuples for compound keys: {(row, col): val for ...}.

4. Deleting during iteration: for k in d: del d[k] raises RuntimeError: dictionary changed size during iteration. Iterate over list(d) first, or build a new dict.


Dicts are one of the few Python features you never outgrow — every codebase uses them, and the difference between junior and senior code often is HOW they're used. Learn .get() / .setdefault() / defaultdict / Counter in your first year and you skip a full class of production bugs.

Next step: 12 hands-on dictionary lessons in Foundations with real Python running in your browser and an AI mentor on every step.

Ready to try it?

15 lessons free after a free signup — no card. Sample the platform, then decide.

Start free

Or unlock every lesson

Pro — $12/mo, unlimited AI, cancel any time. Refund within 14 days worldwide.

See Pro plans

Get one Python lesson + one career idea every Friday

No spam, no "buy our course now". Three bullets, every Friday. Unsubscribe with one click.