Back to Home
Python

Python Dictionaries: The Complete Guide

Olatunji Azeez
April 21, 2026
0 views
Python Dictionaries: The Complete Guide

Python Dictionaries: The Complete Guide

If you ask experienced Python developers which built-in data structure they use most, the dictionary almost always tops the list. It's fast, flexible, and built into the core of how Python itself works — from tracking variable scopes to powering class attribute lookups. Whether you're processing JSON, building configuration systems, or just mapping one thing to another, dictionaries are the tool you'll reach for again and again.

What Is a Python Dictionary?

A dictionary is a collection of key-value pairs. Each key acts as a unique identifier, and you use it to store or retrieve an associated value. Here's a simple example:

server_config = {

                         "host": "localhost",

                           "port": 8080,

                          "debug": True,

                          "timeout": 30,

                       }

print(server_config["host"])   # localhost

print(server_config["port"])   # 8080

Dictionaries are central to Python internals too. The built-in globals() function returns a dictionary of all names defined in the current global scope. When you define a class, Python stores its instance attributes in a dictionary accessible via .__dict__:

class Car:

     def init(self, make, model):

              self.make = make

              self.model = model

my_car = Car("Toyota", "Corolla")

print(my_car.__dict__)

# {'make': 'Toyota', 'model': 'Corolla'}

Key Properties

Python dictionaries have several characteristics worth knowing upfront:

  • Mutable — you can add, modify, and remove entries after creation

  • Dynamic — they grow and shrink automatically as you add or remove data

  • Ordered — since Python 3.7, insertion order is guaranteed and preserved

  • Fast — implemented as hash tables, so lookups are extremely efficient regardless of dictionary size

Rules for Keys and Values

Not everything can be a dictionary key. Keys must be hashable — meaning their value must remain constant throughout their lifetime. In practice, this means strings, numbers, and tuples of immutable items all work as keys, while lists, sets, and other dictionaries do not.

# These all work as keys


valid = {


    "name": "Alice",


    42: "answer",


    (1, 2): "a tuple key",


}



# This raises a TypeError

broken = {[1, 2, 3]: "list as key"}

# TypeError: unhashable type: 'list'

Keys must also be unique within a dictionary. If you assign a value to a key that already exists, the old value is replaced — no error is raised, and no duplicate entry is created.

Dictionary values, on the other hand, have no restrictions at all. They can be any Python object — numbers, strings, lists, functions, other dictionaries, or custom class instances — and duplicates are perfectly fine.


Creating Dictionaries

There are three main ways to create a dictionary in Python.

1. Dictionary Literals

The most common approach uses curly braces with colon-separated key-value pairs:

person = {


    "first_name": "Sarah",

    "last_name": "Mitchell"

    "age": 29,

    "city": "Edinburgh",

}



For an empty dictionary, use an empty pair of curly braces:

data = {}


Note: Don't confuse this with sets. A set also uses curly braces ({1, 2, 3}), but it contains individual values rather than pairs. To create an empty set, you must use set() — empty curly braces always produce an empty dictionary.

2. The dict() Constructor

The dict() function gives you more flexibility in how you build a dictionary. You can call it in several ways:

From keyword arguments (keys must be valid Python identifiers):

person = dict(first_name="Sarah", last_name="Mitchell", age=29)

From a list of tuples:

person = dict([("first_name", "Sarah"), ("last_name", "Mitchell"), ("age", 29)]

From two parallel lists using zip():

keys = ["first_name", "last_name", "age"]

values = ["Sarah", "Mitchell", 29]

person = dict(zip(keys, values))

This zip() pattern is handy when your keys and values arrive as separate sequences — it pairs them up in order and hands them to dict().

3. The .fromkeys() Class Method

When you need to create a dictionary with a set of predefined keys but don't have the values yet, .fromkeys() is useful:

products = dict.fromkeys(["apple", "banana", "mango", "grape"], 0)

print(products)

# {'apple': 0, 'banana': 0, 'mango': 0, 'grape': 0}


This creates a dictionary where every key maps to the same default value (0 here, or None if you don't specify one). It's a concise way to initialise a tracking or counting structure.


Accessing Values

To retrieve a value, use the key in square brackets:

print(person["first_name"])  # Sarah

If you try to access a key that doesn't exist, Python raises a KeyError:

print(person["middle_name"])

# KeyError: 'middle_name'

To avoid this, use the .get() method instead. It returns None by default if the key is missing, or a custom value you supply:

print(person.get("middle_name"))        # None

print(person.get("middle_name", "N/A")) # N/A


For nested dictionaries or mixed structures, chain the keys (and indices where needed):

employee = {

    "name": "David",
     "skills": ["Python", "SQL", "Docker"],

  "address": {"city": "London", "postcode": "EC1A"},

}

print(employee["skills"][1])            # SQL

print(employee["address"]["city"])      # London



Building Dictionaries Incrementally

You don't always have all your data available up front. Dictionaries can be populated on the fly in several ways.

Manual Assignment

Start with an empty dictionary and add keys one at a time:

stats = {}

stats["total_users"] = 1500

stats["active_today"] = 312

stats["new_signups"] = 47

Using a Loop

When constructing a dictionary from a sequence, a for loop works well:

cubes = {}

for n in range(1, 8):

   cubes[n] = n ** 3

print(cubes)


# {1: 1, 2: 8, 3: 27, 4: 64, 5: 125, 6: 216, 7: 343}

Dictionary Comprehensions

For a more concise version of the above, use a dictionary comprehension:

cubes = {n: n ** 3 for n in range(1, 8)}

You can also filter with a condition:

even_squares = {n: n ** 2 for n in range(1, 11) if n % 2 == 0}

# {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

Comprehensions are clean, readable, and generally faster than the loop equivalent.


Dictionary Methods

Python's dict type comes with a rich set of methods. Here's a tour of the most useful ones.

Viewing Data

.keys() returns a view of all keys:

inventory = {"apple": 50, "banana": 30, "mango": 20}

print(inventory.keys())

# dict_keys(['apple', 'banana', 'mango'])

.values() returns a view of all values:

print(inventory.values())

# dict_values([50, 30, 20])

.items() returns a view of all key-value pairs as tuples:

print(inventory.items())

# dict_items([('apple', 50), ('banana', 30), ('mango', 20)])

These views are dynamic — they reflect any changes made to the dictionary after they're created. The .keys() view also supports set operations like union and intersection, which can be handy for comparing two dictionaries' key sets.

Adding and Updating

.setdefault(key, default=None) inserts a key with a default value only if that key isn't already present. If the key exists, it returns the existing value unchanged:

inventory.setdefault("grape", 0)   # Adds 'grape': 0

inventory.setdefault("apple", 999) # 'apple' already exists — no change

print(inventory["apple"])          # 50

.update(other) merges another dictionary (or iterable of pairs) into the current one. Existing keys get their values updated; new keys are appended:

defaults = {"timeout": 30, "retries": 3, "debug": False}

overrides = {"debug": True, "log_level": "INFO"}

defaults.update(overrides)

print(defaults)

# {'timeout': 30, 'retries': 3, 'debug': True, 'log_level': 'INFO'}

You can also pass keyword arguments directly to .update():

defaults.update(timeout=60, version="2.0")

Removing Data

.pop(key) removes an entry by key and returns its value. Optionally provide a fallback to avoid a KeyError when the key is absent:

removed = inventory.pop("banana")

print(removed)   # 30

inventory.pop("pineapple", None)  # No error if key doesn't exist

.popitem() removes and returns the most recently inserted key-value pair as a tuple (last-in, first-out order):

last_item = inventory.popitem()

# Returns something like ('mango', 20)

del removes an entry without returning the value:

del inventory["apple"]

.clear() removes all entries, leaving an empty dictionary:

inventory.clear()

print(inventory)  # {}

Operators With Dictionaries

Membership Testing: in and not in

Check whether a key exists in a dictionary:

config = {"host": "localhost", "port": 3000}

print("host" in config)      # True

print("timeout" in config)   # False

print("port" not in config)  # False

This checks keys only, not values.

Equality: == and !=

Two dictionaries are equal if they contain the same key-value pairs, regardless of insertion order:

a = {"x": 1, "y": 2}

b = {"y": 2, "x": 1}

print(a == b)  # True

Merging With | and |=

Python 3.9 introduced the | operator for merging two dictionaries into a new one:

base = {"color": "blue", "size": "M"}

updates = {"size": "L", "material": "cotton"}

merged = base | updates

print(merged)

# {'color': 'blue', 'size': 'L', 'material': 'cotton'}

The |= operator merges in-place (modifying the left-hand dictionary directly):

base |= updates

This is functionally similar to .update() but with a cleaner syntax when working with two dictionaries.


Built-in Functions With Dictionaries

Many of Python's built-in functions work naturally with dictionaries.

len() — number of key-value pairs:

print(len({"a": 1, "b": 2, "c": 3}))  # 3

sum() — total of all values (when they're numeric):

scores = {"Alice": 88, "Bob": 74, "Carol": 95}

print(sum(scores.values()))  # 257

min() and max() — smallest and largest values:

print(min(scores.values()))  # 74

print(max(scores.values()))  # 95

To find the key with the highest value, use max() with a key argument:

print(max(scores, key=scores.get))  # Carol

sorted() — sort by keys, values, or items:

# Sort by key alphabetically

print(sorted(scores))

# ['Alice', 'Bob', 'Carol']

# Sort by value, highest first

print(sorted(scores, key=scores.get, reverse=True))

# ['Carol', 'Alice', 'Bob']

any() and all() — check truthiness across values:

flags = {"feature_a": True, "feature_b": False, "feature_c": True}

print(any(flags.values()))   # True — at least one is True

print(all(flags.values()))   # False — not all are True


Iterating Over Dictionaries

Looping over a dictionary is something you'll do constantly in Python.

Iterating Over Keys

By default, iterating over a dictionary gives you its keys:

config = {"host": "localhost", "port": 8080, "debug": True}

for key in config:

    print(key)

# host

# port

# debug

You can also call .keys() explicitly — the behaviour is the same:

for key in config.keys():

    print(key)

Iterating Over Values

Use .values() when you only need the values:

for value in config.values():

    print(value)

# localhost

# 8080

# True

Iterating Over Key-Value Pairs

The most common loop pattern uses .items() to unpack both at once:

for key, value in config.items():
    print(f"{key}: {value}")

# host: localhost

# port: 8080

# debug: True

This pattern is clean and avoids having to do config[key] inside the loop body.


Dictionary-Like Classes in the Standard Library

The collections module provides several specialised dictionary variants worth knowing about:

  • OrderedDict — preserves insertion order explicitly and supports order-based operations (less necessary since Python 3.7, but still useful for some ordering-specific logic)

  • defaultdict — automatically creates a default value for missing keys, which avoids KeyError and simplifies counting or grouping patterns

  • ChainMap — bundles multiple dictionaries together so they can be searched as one, without merging them

  • Counter — designed specifically for counting hashable objects; behaves like a dictionary where keys are items and values are counts

For example, defaultdict makes a word-frequency counter trivially easy:

from collections import defaultdict

word_count = defaultdict(int)

for word in "the cat sat on the mat the cat".split():

    word_count[word] += 1

print(dict(word_count))

# {'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1}

Without defaultdict, you'd need to check whether each key exists before incrementing — or use .setdefault(). The defaultdict handles that automatically.


Summary

Python dictionaries are one of the most useful and versatile data structures in the language. Here's a quick recap of the ground covered:

  • Create dictionaries with {} literals, dict(), or .fromkeys()

  • Access values with square bracket notation or .get() for safe lookups

  • Add and update entries with direct assignment, .setdefault(), or .update()

  • Remove entries with .pop(), .popitem(), del, or .clear()

  • Use in to test for key membership, | to merge dictionaries, and == to compare them

  • Loop over keys, values, or pairs using for loops with .keys(), .values(), or .items()

  • Reach for defaultdict, Counter, or ChainMap from collections when the standard dict isn't quite enough

The more comfortable you become with dictionaries, the more naturally you'll reach for them when organising, processing, and transforming data in your Python projects.


Have a question about dictionaries or want to see a specific pattern covered? Leave a comment below.

Share this article

Loading comments...