Nested Dictionaries in Python

Learn how to create, access, and modify nested dictionaries in Python. Build multi-level data structures and avoid the common pitfalls of shared references.

5 min read

A nested dictionary is a dictionary whose values are themselves dictionaries. While a flat dictionary maps keys to simple values like numbers or strings, a nested dictionary organizes data into a hierarchy. The outer dictionary's keys are categories, sections, or parent identifiers, and each inner dictionary holds the details for that category. If you have ever worked with JSON data from a web API, you have already used nested dictionaries: JSON objects map directly to Python dictionaries, and a JSON document with nested objects becomes a nested dictionary structure.

The skills from earlier articles in this section apply directly to nested dictionaries. Accessing an inner value uses the same square-bracket syntax, just chained across multiple levels. The get method for safe access works at every level. And the caution from the article on copying Python lists about shallow copies applies even more forcefully to nested dictionaries: a shallow copy of a nested dictionary shares the inner dictionaries with the original, and modifying an inner dictionary through the copy changes it for the original as well.

Creating nested dictionaries

The most direct way to create a nested dictionary is to write it as a literal with inner dictionaries indented for readability. Each level of indentation corresponds to a level of nesting, and the structure mirrors the JSON format that nested dictionaries often represent.

pythonpython
users = {
    "alice": {"role": "admin", "active": True},
    "bob": {"role": "editor", "active": False},
}

This creates a dictionary where the key "alice" maps to an inner dictionary with two keys, and the key "bob" maps to a different inner dictionary with its own set of values. Accessing the role of a specific user requires chaining the outer and inner keys: users["alice"]["role"] returns "admin".

For programmatic construction, where you do not know the keys ahead of time, you build nested dictionaries incrementally. The most important rule is to create each inner dictionary as a new, independent object. A common beginner mistake is to use the same dictionary literal or the same dict constructor call for multiple outer keys, which results in all those keys sharing a single inner dictionary. Changing one then silently changes all the others.

pythonpython
config = {}
for section in ["database", "cache", "logging"]:
    config[section] = {}

Each iteration creates a fresh empty dictionary and assigns it to a different outer key. The three inner dictionaries are independent, and modifying one section's configuration will not affect the others. This pattern, creating inner dictionaries inside a loop, is the safe way to build nested structures dynamically.

Safe access with get

Deeply nested access with square brackets is fragile: if any intermediate key is missing, Python raises a KeyError and the entire chain fails. For JSON data from external sources, where you cannot guarantee that every optional field is present, chained calls to the get method provide a safe alternative.

The pattern is to call get on each dictionary in the chain, providing an empty dictionary as the default for every level except the last. If a key is missing, get returns the empty default, and the next get call in the chain safely returns its own default instead of raising an error. The final get provides the actual fallback value for the data you are trying to retrieve.

This pattern is verbose when written out fully, but it is the standard way to handle optional nested keys without try/except blocks. For deeply nested structures with many optional levels, helper libraries and the glom package exist, but for most beginner and intermediate use cases, chained get calls or a single try/except at the outer level are sufficient and keep the code readable.

Nested dictionaries from comprehensions

Dictionary comprehensions can create nested structures directly. The value expression in a comprehension can itself be another dictionary comprehension, building the inner dictionary for each outer key in a single statement. This is the most expressive way to create a nested dictionary when the inner structure follows a pattern.

pythonpython
teams = ["red", "blue"]
players = ["ana", "sam"]
rosters = {team: {p: 0 for p in players} for team in teams}

The comprehension reads from the inside out: the inner comprehension builds a fresh dictionary mapping each player to a starting score of 0, and the outer comprehension maps each team name to its own copy of that inner dictionary. The result, rosters, gives every team an independent scoreboard, so updating rosters["red"]["ana"] never touches rosters["blue"]. This pattern mirrors the nested list comprehensions covered in list comprehensions in Python, applied to dictionaries instead of lists.

Defaultdict for auto-creating inner dictionaries

The defaultdict class from the collections module simplifies nested dictionary construction by automatically creating inner dictionaries when a new key is accessed. When you create a defaultdict with dict as the default factory, accessing a missing key creates an empty dictionary at that key and returns it, ready for further assignment.

pythonpython
from collections import defaultdict
grades = defaultdict(dict)
grades["Alice"]["math"] = 95

The first line accesses grades["Alice"], which does not yet exist. Instead of raising a KeyError, defaultdict creates an empty dictionary at that key and returns it. The second line then assigns 95 to the key "math" inside that newly created inner dictionary. This pattern eliminates the need to check whether an inner dictionary exists before assigning to it, and it is the standard approach for building nested dictionaries when the structure grows organically as data arrives.

For further exploration of how Python handles dynamic data structures that grow during program execution, the article on iterating through Python collections covers patterns for processing nested and hierarchical data efficiently.

Rune AI

Rune AI

Key Insights

  • A nested dictionary is a dictionary whose values are themselves dictionaries.
  • Access nested values by chaining square brackets: dict[key1][key2].
  • Use get() with a default at each level to safely access deeply nested keys.
  • Create each inner dictionary independently to avoid shared-reference bugs.
  • The defaultdict with dict as the factory auto-creates missing inner dictionaries.
RunePowered by Rune AI

Frequently Asked Questions

How do I access a value deep inside a nested dictionary?

Chain square-bracket accesses: dict[outer_key][inner_key]. Each pair of brackets drills one level deeper. If any intermediate key is missing, Python raises a KeyError. To avoid this, use the get() method at each level with a default of an empty dictionary, or use a try/except block to catch the KeyError.

What is the cleanest way to create a nested dictionary of arbitrary depth?

Use the defaultdict from the collections module with dict as the default factory. This creates inner dictionaries automatically when a new key is accessed. For JSON-like nested structures of fixed depth, a dictionary comprehension or a recursive function that builds the structure level by level is the clearest approach.

Why does modifying a nested dictionary sometimes change another one?

This happens when the same inner dictionary object is shared between two outer dictionaries. Assignment creates references, not copies. If you initialize multiple outer keys with the same inner dictionary literal, all those keys point to a single shared inner dictionary. Create each inner dictionary fresh, either with a comprehension or by calling dict() for each entry.

Conclusion

Nested dictionaries model hierarchical data naturally in Python. They are the native format for parsed JSON, configuration with sections, and any data that organizes into categories and subcategories. Understanding how to access, create, and safely copy nested dicts prevents the shared-reference bugs that are among the most frustrating for beginners.