Python Dictionaries: Key-Value Pairs, Methods, and Real-World Patterns
Dictionaries are the backbone of Python data handling. JSON responses from APIs, configuration files, database rows, HTML attributes — almost any structured data maps naturally to a dictionary. Python’s dict is efficient, flexible, and has an excellent set of built-in methods.
Creating Dictionaries
# Literal syntaxperson = { "name": "Alice", "age": 30, "city": "Edinburgh"}
# dict() constructor with keyword argumentssettings = dict(theme="dark", font_size=14, auto_save=True)
# From a list of (key, value) pairspairs = [("a", 1), ("b", 2), ("c", 3)]lookup = dict(pairs)
# From two lists using zipkeys = ["x", "y", "z"]values = [10, 20, 30]coords = dict(zip(keys, values))
# Empty dictempty = {}Keys must be immutable (strings, numbers, tuples). Values can be anything.
Accessing Values
Square bracket notation
user = {"name": "Bob", "email": "bob@example.com", "active": True}
print(user["name"]) # "Bob"print(user["email"]) # "bob@example.com"If the key doesn’t exist, you get a KeyError:
print(user["phone"]) # KeyError: 'phone'get() — the safe alternative
get() returns None (or a default you specify) when the key doesn’t exist:
print(user.get("name")) # "Bob"print(user.get("phone")) # Noneprint(user.get("phone", "N/A")) # "N/A" — custom defaultUse get() whenever you’re not certain a key exists. user["phone"] crashing is a runtime error; user.get("phone") returning None is a controllable situation.
setdefault()
Returns the value if the key exists; if not, inserts the key with a default value and returns it:
inventory = {"apples": 5}inventory.setdefault("bananas", 0) # adds bananas: 0inventory.setdefault("apples", 0) # no change — apples already existsprint(inventory) # {"apples": 5, "bananas": 0}Useful for building up nested structures or counters.
Modifying Dictionaries
config = {"debug": False, "host": "localhost", "port": 8080}
# Update or add a keyconfig["debug"] = Trueconfig["database"] = "postgres"
# Update multiple keys at onceconfig.update({"port": 9000, "timeout": 30})
# Remove a keydel config["timeout"]removed = config.pop("database") # removes and returns the valueconfig.pop("missing", None) # safe pop — no error if key absentIterating Over Dictionaries
Since Python 3.7, dictionaries maintain insertion order:
scores = {"Alice": 92, "Bob": 85, "Charlie": 78}
# Keys only (default iteration)for name in scores: print(name)
# Values onlyfor score in scores.values(): print(score)
# Key-value pairs — the most common patternfor name, score in scores.items(): print(f"{name}: {score}")Merging Dictionaries
defaults = {"theme": "light", "language": "en", "notifications": True}user_prefs = {"theme": "dark", "font_size": 14}
# Python 3.9+ — union operatormerged = defaults | user_prefs # user_prefs wins on conflicts
# Python 3.5+ — dict unpackingmerged = {**defaults, **user_prefs} # same behaviour
# In-place mergedefaults.update(user_prefs) # modifies defaults
print(merged)# {'theme': 'dark', 'language': 'en', 'notifications': True, 'font_size': 14}defaultdict from collections
When you need to handle missing keys with a factory function, defaultdict is cleaner than repeated setdefault() calls:
from collections import defaultdict
# Count word occurrencestext = "apple banana apple cherry banana apple"word_count = defaultdict(int) # missing keys default to 0
for word in text.split(): word_count[word] += 1 # no KeyError on first access
print(dict(word_count)) # {'apple': 3, 'banana': 2, 'cherry': 1}
# Build lists of values per keyfrom collections import defaultdict
groups = defaultdict(list)students = [("Math", "Alice"), ("Science", "Bob"), ("Math", "Charlie")]
for subject, student in students: groups[subject].append(student)
print(dict(groups))# {'Math': ['Alice', 'Charlie'], 'Science': ['Bob']}Comprehensions
Dict comprehensions build dictionaries in a single expression:
names = ["Alice", "Bob", "Charlie"]name_lengths = {name: len(name) for name in names}# {'Alice': 5, 'Bob': 3, 'Charlie': 7}
# Filter while buildingscores = {"Alice": 92, "Bob": 65, "Charlie": 78, "Diana": 88}passing = {name: score for name, score in scores.items() if score >= 70}# {'Alice': 92, 'Charlie': 78, 'Diana': 88}
# Invert a dictionary (swap keys and values)original = {"a": 1, "b": 2, "c": 3}inverted = {v: k for k, v in original.items()}# {1: 'a', 2: 'b', 3: 'c'}Common Real-World Patterns
Counting occurrences
from collections import Counter
words = "the cat sat on the mat the cat".split()counts = Counter(words)print(counts.most_common(3)) # [('the', 3), ('cat', 2), ('sat', 1)]Grouping records
orders = [ {"id": 1, "status": "shipped", "amount": 50}, {"id": 2, "status": "pending", "amount": 75}, {"id": 3, "status": "shipped", "amount": 120},]
by_status = defaultdict(list)for order in orders: by_status[order["status"]].append(order)Caching computed results
cache = {}
def expensive_calculation(n): if n in cache: return cache[n] result = sum(range(n)) # simulate heavy computation cache[n] = result return resultPractical Tips
Use .get() instead of in + [] when you need the value anyway:
# Redundantif "key" in d: value = d["key"]
# Bettervalue = d.get("key")Keys are case-sensitive. {"Name": "Alice"} and {"name": "Alice"} are different keys. If accepting user-supplied keys, normalise case consistently.
Avoid modifying a dictionary while iterating it. Create a list of changes to apply after iteration, or iterate over a copy.