Skip to content
C Codeloom
Python

Python Dictionaries: Python's Most Useful Data Structure

A complete beginner's guide to Python dictionaries — creation, access, mutation, iteration, the essential methods, and the patterns that appear in nearly every real Python program.

·7 min read · By Yash Kesharwani
Beginner 10 min read

What you'll learn

  • How dictionaries store key-value pairs
  • Every common operation: access, set, delete, check membership
  • The most-used dictionary methods
  • Three patterns that appear in nearly every real Python program
  • When to use a dictionary vs. another container

Prerequisites

  • Comfortable with lists and tuples — see Lists and Tuples

If you can use only one Python container well, make it the dictionary. Almost every program of meaningful size uses dictionaries somewhere — for configuration, for JSON data, for caches, for counting, for grouping. They are also the cleanest representation of objects with named attributes before you reach for classes.

What a dictionary is

A dictionary stores associations between keys and values. You give Python a key, and it gives you back the associated value — very quickly, regardless of how many entries the dictionary holds.

user = {
    "name": "Alice",
    "age": 30,
    "is_admin": False,
}

The example above maps the string "name" to the string "Alice", the string "age" to the integer 30, and so on.

You read entries with square-bracket access:

print(user["name"])    # Alice
print(user["age"])     # 30

Creating dictionaries

Several ways to make one:

# Literal — most common
scores = {"alice": 90, "bob": 75}

# Empty
empty = {}

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

# Keyword arguments — only works with valid identifier keys
d = dict(alice=90, bob=75)

Keys must be hashable — meaning strings, numbers, and tuples of immutables are valid; lists and dictionaries are not.

# Legal
{"a": 1, 42: "answer", (0, 0): "origin"}

# Illegal
{[1, 2]: "value"}     # TypeError: unhashable type: 'list'

Values can be anything — numbers, strings, lists, other dictionaries, functions, your own objects.

Setting, updating, and deleting

A dictionary is mutable. Use square-bracket assignment to add a new key or update an existing one:

user = {"name": "Alice", "age": 30}
user["email"] = "alice@example.com"   # add new key
user["age"] = 31                      # update existing key
print(user)
# {'name': 'Alice', 'age': 31, 'email': 'alice@example.com'}

Remove a key with del or pop:

del user["email"]            # removes the key
age = user.pop("age")        # removes & returns the value
print(age)                   # 31

pop accepts a default value for missing keys, which prevents a KeyError:

phone = user.pop("phone", "N/A")
print(phone)                 # N/A

Accessing safely with .get()

Square-bracket access raises KeyError if the key is missing:

user = {"name": "Alice"}
print(user["age"])           # KeyError: 'age'

When a key may not be present, use .get(). It returns None by default, or a value you specify:

print(user.get("age"))            # None
print(user.get("age", "unknown")) # unknown

.get() is the safer default in most code — reach for [] only when a missing key would be a genuine bug worth halting on.

Checking membership

Use in to test whether a key exists. This is a constant-time check regardless of dictionary size — one of the dictionary’s defining strengths:

user = {"name": "Alice", "age": 30}
print("name" in user)        # True
print("email" in user)       # False

in checks keys by default, not values. To check values, use in user.values().

Iterating

Three views drive most iteration:

user = {"name": "Alice", "age": 30, "role": "admin"}

# Iterate over keys (default)
for key in user:
    print(key)

# Iterate over values
for value in user.values():
    print(value)

# Iterate over key-value pairs
for key, value in user.items():
    print(key, value)

.items() is the form you’ll use most. It returns each entry as a (key, value) tuple that unpacks cleanly into two names.

Since Python 3.7 dictionaries preserve insertion order. If you add keys in a certain order, you will iterate them in that order. This is a guarantee, not an implementation detail.

The most-used methods

Beyond .get() and .pop(), three more methods earn their keep:

.update() — merge another dictionary in

defaults = {"theme": "light", "font_size": 14}
user_settings = {"theme": "dark"}
defaults.update(user_settings)
print(defaults)    # {'theme': 'dark', 'font_size': 14}

.setdefault() — get the value, inserting a default if missing

counts = {}
counts.setdefault("apple", 0)
counts["apple"] += 1
print(counts)      # {'apple': 1}

This pattern is so common it has a dedicated container in the standard library — collections.defaultdict, which we’ll meet later.

.keys(), .values(), .items()

All three return views — special objects that reflect the dictionary as it changes. Convert them to a list with list(...) if you need a snapshot.

Three patterns to commit to memory

The following three patterns appear in nearly every real Python program. Understanding them puts you well ahead of where most beginners stop.

1. Counting

How many times does each item appear in a collection?

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

print(counts)    # {'apple': 3, 'banana': 2, 'cherry': 1}

A cleaner version uses collections.Counter:

from collections import Counter
print(Counter(words))    # Counter({'apple': 3, 'banana': 2, 'cherry': 1})

2. Grouping

How do you bucket items by some attribute?

people = [
    {"name": "Alice", "role": "admin"},
    {"name": "Bob",   "role": "user"},
    {"name": "Carol", "role": "admin"},
]

by_role = {}
for person in people:
    by_role.setdefault(person["role"], []).append(person["name"])

print(by_role)
# {'admin': ['Alice', 'Carol'], 'user': ['Bob']}

3. Lookup tables

When a long chain of if/elif/else checks the same variable, a dictionary makes it shorter and faster:

status_messages = {
    200: "OK",
    404: "Not Found",
    500: "Server Error",
}
code = 404
print(status_messages.get(code, "Unknown"))    # Not Found

Try it yourself. Given the list votes = ["yes", "no", "yes", "yes", "maybe", "no", "yes"], write a small program that counts how many times each option appears using the counting pattern above. Confirm "yes" appears 4 times.

Dictionary comprehensions

Like list comprehensions, dictionary comprehensions build a dictionary inline:

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

A common use is inverting a dictionary — swapping keys and values:

codes = {"en": "English", "es": "Spanish", "fr": "French"}
inverse = {value: key for key, value in codes.items()}
print(inverse)    # {'English': 'en', 'Spanish': 'es', 'French': 'fr'}

Only works cleanly when the original values are unique.

Nested dictionaries

A value in a dictionary can itself be a dictionary. JSON-like data is exactly this structure:

data = {
    "user": {
        "name": "Alice",
        "preferences": {
            "theme": "dark",
            "language": "en",
        },
    },
    "session": {
        "active": True,
    },
}

print(data["user"]["preferences"]["theme"])    # dark

Chained [] access works as long as every intermediate key exists. For uncertain depths, chained .get() is safer:

theme = data.get("user", {}).get("preferences", {}).get("theme", "light")

When to use a dictionary

Reach for a dictionary when:

  • You need to associate keys with values and look them up by key
  • You receive JSON-like data from a file or API
  • You want to count, group, or build a lookup table
  • You need fast membership checks on keys

If you instead need an ordered collection of values without keys, use a list. If you need to test “does this contain X?” without caring about associated values, use a set.

Recap

You now know:

  • A dictionary maps hashable keys to arbitrary values, with fast lookup
  • [], .get(), .pop(), del, and in cover most everyday access
  • .items() is the iteration form you’ll use most often
  • Dictionaries preserve insertion order in Python 3.7+
  • Three patterns — counting, grouping, lookup tables — appear in nearly every real program
  • Dictionary comprehensions make small transformations elegant
  • Use .get() chains for uncertain nested data

Next steps

The next post covers sets — the right tool when you need uniqueness or fast membership testing, and the cleanest way to combine groups of items.

→ Next: Python Sets and Set Operations

Questions or feedback? Email codeloomdevv@gmail.com.