Home Tutorials Data Structures

Data Structures

Dictionary Techniques in Python: Beyond Basic Key-Value Storage

Pyford Notes July 1, 2026 8 min read
Key points
  • Dict comprehensions build dicts from any iterable in a single expression.
  • dict.get(key, default) avoids KeyError for optional keys.
  • defaultdict from collections initialises missing keys automatically.
  • The | operator (Python 3.9+) merges two dicts into a new one without mutation.

Creation methods

A dict can be created in several equivalent ways. Each has a context where it reads more naturally:

# Literal syntax
config = {"host": "localhost", "port": 5432, "ssl": True}

# dict() constructor with keyword arguments
config = dict(host="localhost", port=5432, ssl=True)

# From a list of key-value tuples (common when transforming data)
pairs = [("a", 1), ("b", 2), ("c", 3)]
mapping = dict(pairs)

# From two parallel sequences
keys = ["x", "y", "z"]
vals = [10, 20, 30]
combined = dict(zip(keys, vals))

Dict comprehensions

Like list comprehensions but with a key-value pair separated by a colon:

words = ["apple", "banana", "cherry"]
lengths = {word: len(word) for word in words}
# {"apple": 5, "banana": 6, "cherry": 6}

# Invert a mapping (assumes values are unique)
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: "a", 2: "b", 3: "c"}

# Filter while building
scores = {"Alice": 92, "Bob": 55, "Carol": 78}
passing = {name: score for name, score in scores.items() if score >= 60}
# {"Alice": 92, "Carol": 78}

Safe access with get() and setdefault()

Accessing a missing key raises KeyError. Two methods let you handle this gracefully:

data = {"name": "Alice", "role": "admin"}

# get() returns None (or a default) for missing keys
email = data.get("email")                  # None
email = data.get("email", "not set")       # "not set"

# setdefault() inserts and returns the default if missing
visits = {}
for page in access_log:
    visits.setdefault(page, 0)
    visits[page] += 1

setdefault is particularly useful for accumulating values into a list under a shared key without checking for existence first:

grouped = {}
for item in items:
    grouped.setdefault(item["category"], []).append(item)

defaultdict for automatic initialisation

collections.defaultdict takes a callable. When a missing key is accessed, it calls that callable to produce the initial value:

from collections import defaultdict

word_positions = defaultdict(list)
for i, word in enumerate(text.split()):
    word_positions[word].append(i)
# No KeyError, no setdefault needed

counter = defaultdict(int)
for event in events:
    counter[event["type"]] += 1

The first argument can be any zero-argument callable: list, int, set, or a lambda for more complex defaults like lambda: {"count": 0, "total": 0.0}.

Merging dicts

Python 3.9 introduced the | operator for merging. When keys overlap, the right-hand dict wins:

defaults = {"color": "blue", "size": "medium", "border": True}
overrides = {"color": "red", "border": False}

merged = defaults | overrides
# {"color": "red", "size": "medium", "border": False}

# In-place merge (updates defaults in-place)
defaults |= overrides

For Python 3.8 and earlier, use unpacking: {**defaults, **overrides}. The effect is identical but less readable.

Sorting a dict by value or key

Dicts preserve insertion order in Python 3.7+, but sorted() returns a new sorted structure:

scores = {"Alice": 92, "Bob": 55, "Carol": 78, "Dave": 88}

# Sort by value descending; result is a list of tuples
ranked = sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
# [("Alice", 92), ("Dave", 88), ("Carol", 78), ("Bob", 55)]

# Rebuild as a sorted dict
ranked_dict = dict(ranked)

Counter for frequency analysis

collections.Counter is a dict subclass built for tallying. It accepts any iterable and counts occurrences:

from collections import Counter

words = "the quick brown fox jumps over the lazy dog the".split()
freq = Counter(words)
print(freq.most_common(3))
# [("the", 3), ("quick", 1), ("brown", 1)]

# Counter supports arithmetic
a = Counter(["x", "y", "x"])
b = Counter(["y", "y", "z"])
print(a + b)   # Counter({"y": 3, "x": 2, "z": 1})
print(a - b)   # Counter({"x": 2})   (drops non-positive counts)
Iteration order Since Python 3.7, regular dict maintains insertion order as part of the language specification, not just as an implementation detail. You can rely on for k, v in d.items() yielding items in the order they were inserted.