# Introduction
Python dictionaries are incredibly versatile — they power everything from configuration settings and JSON parsing to handling API responses. Most newcomers only pick up the fundamentals: creating a dictionary, looking up a key, and modifying a value. That barely scratches the surface, though. In this guide, we’ll explore 7 practical techniques that will help you write cleaner, more idiomatic Python code. Let’s dive in.
# Prefer .get() Over [] When Accessing Values
Imagine you’re working with a dictionary and need to retrieve a specific value. What happens if that key doesn’t exist? Suppose you have a configuration dictionary and you attempt to access the "timeout" key like so:
config = {"debug": True, "verbose": False}
print(config["timeout"])
Output:
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
----> 2 print(config["timeout"])
KeyError: 'timeout'
This crashes with a KeyError because "timeout" isn’t present in the dictionary. A better approach is to use the .get() method. It’s far more forgiving, and you can specify a fallback value to return when the key is absent.
config = {"debug": True, "verbose": False}
print(config.get("timeout", 30))
Output:
This prints 30, the default we provided. That said, if a missing key signals an actual bug in your logic, stick with square brackets — you want the error to surface immediately so you can catch it.
# Leverage defaultdict for Grouping and Counting
Say you have a list of words and you need to tally how often each one appears. You might write something like this:
words = ["apple", "banana", "apple", "cherry", "banana", "banana"]
counts = {}
for word in words:
if word not in counts:
counts[word] = 0
counts[word] += 1
print(counts)
Output:
{'apple': 2, 'banana': 3, 'cherry': 1}
This works, but it’s unnecessarily wordy. Python’s defaultdict streamlines the whole thing:
from collections import defaultdict
words = ["apple", "banana", "apple", "cherry", "banana", "banana"]
counts = defaultdict(int)
for word in words:
counts[word] += 1
print(counts)
Output:
defaultdict(, {'apple': 2, 'banana': 3, 'cherry': 1})
Because we initialized it with defaultdict(int), Python automatically assigns a default value of 0 any time a nonexistent key is accessed.
# Merge Dictionaries Using the | Operator
In current versions of Python, the most elegant way to combine two dictionaries is with the | operator.
defaults = {"color": "blue", "size": "medium"}
overrides = {"size": "large", "weight": "heavy"}
merged = defaults | overrides
print(merged)
Output:
{'color': 'blue', 'size': 'large', 'weight': 'heavy'}
When both dictionaries share a key, the one on the right takes priority. If you’d rather update a dictionary in place, the |= operator does the trick:
defaults |= overrides
print(defaults)
Output:
{'color': 'blue', 'size': 'large', 'weight': 'heavy'}
# Unpack Dictionaries into Function Parameters
Suppose you have a function and a dictionary whose keys align with the function’s parameters. Rather than manually mapping each key — like name=data["name"], age=data["age"] — you can pass everything at once using the ** (double-asterisk) operator. Let’s set up a user-creation function and some sample data to illustrate:
def create_user(name, age, role="viewer"):
return {"name": name, "age": age, "role": role}
user_data = {
"name": "David",
"age": 33
}
# Manual approach
user = create_user(
name=user_data["name"],
age=user_data["age"],
role=user_data["role"]
)
print(user)
Output:
{'name': 'David', 'age': 33, 'role': 'viewer'}
# Using ** unpacking
print(create_user(**user_data))
Output:
{'name': 'David', 'age': 33, 'role': 'viewer'}
Keep in mind that the “Manual approach” above would actually throw a KeyError since user_data lacks a "role" key. The ** unpacking method gracefully falls back on the function’s default value for role, making it
both cleaner and more robust.
# Working With the Walrus Operator on Dictionaries
Python 3.8 brought in the walrus operator (:=), which allows you to assign a value within an expression itself. This feature pairs especially well with dictionaries.
Imagine you have a dictionary and you need to retrieve the user data along with their name, provided they exist. Here’s the conventional approach most developers would take:
data = {
"user": {
"name": "Bryan",
"email": "bryan@gmail.com"
}
}
if data.get("user") is not None:
user = data.get("user")
name = user.get("name")
print(name)
Output:
This approach works, but it performs the same dictionary lookup more than once. You can streamline it using the walrus operator (:=), which handles both the lookup and the assignment in one go:
if (user := data.get("user")) is not None:
name = user.get("name")
print(name)
Output:
This technique becomes particularly valuable when dealing with deeply nested dictionary structures.
# Leveraging TypedDict for Well-Defined Data
Dictionaries are highly flexible, yet that same flexibility can occasionally introduce issues. Consider this example:
def greet(user):
return f"Hello, {user['name']}!"
user = {
"name": "Clair",
"age": "thirty"
}
print(greet(user))
Output:
This runs without errors, but there’s a concealed issue: "age" should be an integer, not a string. Python won’t flag this on its own, which can introduce subtle bugs down the line in larger codebases. TypedDict addresses this by making the expected dictionary schema explicit:
from typing import TypedDict
class UserProfile(TypedDict):
name: str
age: int
def greet(user: UserProfile) -> str:
return f"Hello, {user['name']}!"
Now utilities such as mypy can detect errors before execution:
user: UserProfile = {
"name": "Clair",
"age": "thirty",
}
print(greet(user))
Output:
test.py:15: error: Incompatible types (expression has type "str", TypedDict item "age" has type "int") [typeddict-item]
Found 1 error in 1 file (checked 1 source file)
For more rigorous validation needs, alternatives like dataclasses or Pydantic are frequently the preferred choice.
# Streamlined Iteration With .items(), .keys(), and .values()
Python dictionaries offer a variety of built-in methods for looping through data: .items(), .keys(), and .values(). While most developers are aware of these methods, they don’t reach for them as often as they could. A common pattern might look like this:
scores = {
"David": 92,
"Bryan": 87,
"Clair": 95
}
for name in scores:
print(name, scores[name])
Output:
David 92
Bryan 87
Clair 95
That gets the job done, but it’s far from ideal — it performs an additional dictionary lookup on every iteration. Python’s .items() method offers a more elegant solution:
for name, score in scores.items():
print(name, score)
Output:
David 92
Bryan 87
Clair 95
It delivers both the key and value as a pair, eliminating redundant lookups and improving code clarity. When you only need the keys, reach for .keys(). Likewise, when only the values matter, use .values().
# Final Thoughts
Python dictionaries may seem straightforward at first glance, yet mastering a handful of essential patterns can dramatically improve your code quality. You can explore more about dictionary-related functions via this link. Capabilities like .get(), defaultdict, unpacking, and TypedDict help cut down on repetitive code and make your programs more dependable.
Kanwal Mehreen is a machine learning engineer and technical writer with a deep passion for data science and the intersection of AI with medicine. She co-authored the ebook “Maximizing Productivity with ChatGPT”. As a Google Generation Scholar 2022 for APAC, she champions diversity and academic excellence. She’s also recognized as a Teradata Diversity in Tech Scholar, Mitacs Globalink Research Scholar, and Harvard WeCode Scholar. Kanwal is a passionate advocate for change, having founded FEMCodes to empower women in STEM fields.



