Toolmingo
Guides7 min read

Python Dictionary: Complete Guide with Methods and Examples

Master Python dictionaries — creation, access, methods, iteration, comprehensions, merging, nested dicts, and common pitfalls with code examples.

Python dictionaries are the most versatile built-in data structure. Every real-world program uses them constantly — JSON data, config objects, lookup tables, counting, grouping. This guide covers everything from basics to advanced patterns.


Quick Reference

Operation Syntax Returns
Create d = {"key": "value"} dict
Get value d["key"] or d.get("key") value or KeyError
Set value d["key"] = value
Delete key del d["key"] or d.pop("key") — or value
Check key "key" in d bool
All keys d.keys() dict_keys
All values d.values() dict_values
All pairs d.items() dict_items
Length len(d) int
Merge (3.9+) d1 | d2 new dict
Update d.update(other)

Creating Dictionaries

# Literal syntax
user = {"name": "Ana", "age": 30, "active": True}

# Empty dict
empty = {}
empty = dict()

# From keyword arguments
config = dict(host="localhost", port=5432, debug=False)

# From list of (key, value) pairs
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs)

# From two lists with zip
keys = ["x", "y", "z"]
values = [10, 20, 30]
d = dict(zip(keys, values))

# All keys with the same default value
scores = dict.fromkeys(["alice", "bob", "carol"], 0)
# {"alice": 0, "bob": 0, "carol": 0}

Accessing Values

Square bracket vs .get()

user = {"name": "Ana", "age": 30}

# Square bracket — raises KeyError if missing
name = user["name"]         # "Ana"
city = user["city"]         # KeyError: 'city'

# .get() — returns None (or default) if missing
city = user.get("city")           # None
city = user.get("city", "unknown") # "unknown"

Rule: Use d["key"] when the key must exist (fail fast). Use d.get(key, default) when the key might be absent.

Nested access

data = {"user": {"address": {"city": "Podgorica"}}}

# Chained access — KeyError if any level missing
city = data["user"]["address"]["city"]   # "Podgorica"

# Safe nested access with get()
city = data.get("user", {}).get("address", {}).get("city", "unknown")

Modifying Dictionaries

user = {"name": "Ana", "age": 30}

# Set / update a value
user["age"] = 31
user["email"] = "ana@example.com"   # add new key

# Delete a key
del user["email"]

# Remove and return a value
age = user.pop("age")           # returns 30, removes key
val = user.pop("missing", 0)    # returns 0, no error

# Remove and return last inserted pair (3.7+ preserves insertion order)
key, val = user.popitem()

# Clear all keys
user.clear()

# Set default if key missing, leave it if present
user.setdefault("role", "viewer")  # sets "role": "viewer" only if absent

Merging Dictionaries

defaults = {"debug": False, "timeout": 30, "retries": 3}
overrides = {"timeout": 60, "verbose": True}

# Python 3.9+ — | operator (non-destructive)
config = defaults | overrides
# {"debug": False, "timeout": 60, "retries": 3, "verbose": True}

# Python 3.9+ — |= update in place
defaults |= overrides

# Python 3.5+ — unpacking (works everywhere)
config = {**defaults, **overrides}

# .update() — modifies in place
defaults.update(overrides)

Right side wins on duplicate keys in all four methods.


Iterating Dictionaries

inventory = {"apples": 5, "bananas": 12, "oranges": 0}

# Keys only (default)
for key in inventory:
    print(key)

# Values only
for count in inventory.values():
    print(count)

# Keys and values together — use .items()
for item, count in inventory.items():
    print(f"{item}: {count}")

# Sorted by key
for item in sorted(inventory):
    print(item, inventory[item])

# Sorted by value (descending)
for item, count in sorted(inventory.items(), key=lambda kv: kv[1], reverse=True):
    print(item, count)

Dictionary Comprehensions

# Basic: square every number
squares = {n: n**2 for n in range(1, 6)}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# With filter: only even squares
even_squares = {n: n**2 for n in range(1, 11) if n % 2 == 0}

# Invert a dictionary (flip 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"}

# Filter keys from an existing dict
big_only = {k: v for k, v in inventory.items() if v > 5}

# Transform values
upper_keys = {k.upper(): v for k, v in original.items()}

# From a list of objects
users = [{"id": 1, "name": "Ana"}, {"id": 2, "name": "Ben"}]
user_by_id = {u["id"]: u for u in users}
# {1: {"id": 1, "name": "Ana"}, 2: {...}}

Counting and Grouping

Counter pattern

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]

# Manual counter
counts = {}
for word in words:
    counts[word] = counts.get(word, 0) + 1
# {"apple": 3, "banana": 2, "cherry": 1}

# Cleaner with setdefault
counts = {}
for word in words:
    counts.setdefault(word, 0)
    counts[word] += 1

# Best: collections.Counter
from collections import Counter
counts = Counter(words)
counts.most_common(2)   # [("apple", 3), ("banana", 2)]

Grouping pattern

records = [
    {"city": "NYC", "score": 90},
    {"city": "LA",  "score": 85},
    {"city": "NYC", "score": 75},
]

# Group records by city
from collections import defaultdict
by_city = defaultdict(list)
for r in records:
    by_city[r["city"]].append(r["score"])
# {"NYC": [90, 75], "LA": [85]}

Nested Dictionaries

# Build a nested structure
company = {
    "engineering": {
        "backend": ["Ana", "Ben"],
        "frontend": ["Carol"],
    },
    "marketing": {
        "seo": ["Dave"],
    },
}

# Access
backend_team = company["engineering"]["backend"]   # ["Ana", "Ben"]

# Safe add without overwriting
company.setdefault("sales", {}).setdefault("outbound", []).append("Eve")

# Flatten to a list of (dept, subdept, member) tuples
flat = [
    (dept, subdept, member)
    for dept, teams in company.items()
    for subdept, members in teams.items()
    for member in members
]

Common Patterns

Default factory with defaultdict

from collections import defaultdict

# Avoid KeyError on first access
word_positions = defaultdict(list)
for i, word in enumerate("the quick brown fox".split()):
    word_positions[word].append(i)
# {"the": [0], "quick": [1], "brown": [2], "fox": [3]}

# Integer default (starts at 0)
counts = defaultdict(int)
counts["missing"] += 1   # no KeyError, starts at 0

Ordered dict (3.7+ dicts are ordered)

Python 3.7+ guarantees insertion order in regular dicts. collections.OrderedDict is only needed when you need move_to_end() or want to signal ordering intent explicitly.

ChainMap for layered lookups

from collections import ChainMap

system_env   = {"DEBUG": "false", "LOG_LEVEL": "error"}
project_env  = {"LOG_LEVEL": "info"}
local_env    = {"DEBUG": "true"}

config = ChainMap(local_env, project_env, system_env)
config["DEBUG"]     # "true"  — local wins
config["LOG_LEVEL"] # "info"  — project wins over system

Performance Tips

Operation Time complexity
d[key] O(1) average
key in d O(1) average
d[key] = val O(1) average
del d[key] O(1) average
len(d) O(1)
Iterate all keys O(n)
  • Prefer d.get(key, default) over if key in d: d[key] — one lookup, not two.
  • Use Counter for frequency counting instead of a manual dict.
  • Use defaultdict to eliminate setdefault boilerplate in loops.
  • When you need an immutable mapping (for hashing), use types.MappingProxyType(d).

Common Mistakes

Mistake Problem Fix
d["missing"] KeyError crash d.get("missing", default)
Mutating dict while iterating RuntimeError Iterate over list(d.keys()) or build a new dict
dict.fromkeys(keys, []) All keys share the same list Use comprehension: {k: [] for k in keys}
Comparing d.keys() with a set Works but unclear Use set(d) == other_set
Using a list as a key TypeError (unhashable) Use a tuple instead
{**d1, **d2} key order Right side wins silently Document merge order clearly

The mutable default trap

# WRONG — all keys share one list object
bad = dict.fromkeys(["a", "b", "c"], [])
bad["a"].append(1)
print(bad)  # {"a": [1], "b": [1], "c": [1]}  ← bug!

# CORRECT — each key gets its own list
good = {k: [] for k in ["a", "b", "c"]}
good["a"].append(1)
print(good)  # {"a": [1], "b": [], "c": []}  ✓

Iterating while mutating

d = {"a": 1, "b": 2, "c": 3}

# WRONG
for key in d:
    if d[key] < 2:
        del d[key]   # RuntimeError: dictionary changed size during iteration

# CORRECT — snapshot keys first
for key in list(d.keys()):
    if d[key] < 2:
        del d[key]

# Or build a new dict
d = {k: v for k, v in d.items() if v >= 2}

FAQ

What's the difference between d[key] and d.get(key)?
d[key] raises KeyError if the key is absent. d.get(key) returns None (or a provided default). Use d[key] when absence is a programming error; use .get() when absence is expected.

Are Python dicts ordered?
Yes — since Python 3.7, dicts preserve insertion order as a language guarantee (CPython 3.6 already did it as an implementation detail). You don't need OrderedDict for ordering anymore.

Can I use a list as a dictionary key?
No. Dict keys must be hashable. Lists are mutable and unhashable. Use a tuple instead: d[(1, 2)] = "pair".

How do I copy a dict safely?
d.copy() or dict(d) creates a shallow copy — nested objects are still shared. For a full deep copy: import copy; copy.deepcopy(d).

What's the fastest way to count occurrences?
collections.Counter(iterable) — it's implemented in C and is faster than a manual loop.

How do I merge two dicts in Python 3.8 and earlier?
{**d1, **d2} works in Python 3.5+. .update() works in all versions. The | operator requires Python 3.9+.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools