Python
Nested Dictionary
Data Structures
Programming Tutorial
Python Tips

How do you create nested dict in Python?

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

Introduction

A nested dictionary is a dictionary where values are themselves dictionaries, creating a tree-like hierarchy. This structure naturally represents JSON data, configuration files, database records with related fields, and any data with parent-child relationships. Python provides several ways to create and work with nested dicts — from literal syntax to defaultdict for auto-vivification.

Method 1: Literal Syntax

The simplest approach — define the entire structure inline:

python
1users = {
2    'alice': {
3        'age': 30,
4        'email': '[email protected]',
5        'address': {
6            'city': 'New York',
7            'zip': '10001'
8        }
9    },
10    'bob': {
11        'age': 25,
12        'email': '[email protected]',
13        'address': {
14            'city': 'San Francisco',
15            'zip': '94102'
16        }
17    }
18}
19
20print(users['alice']['address']['city'])  # 'New York'

Method 2: Building Incrementally

Create the outer dict first, then assign inner dicts:

python
1config = {}
2config['database'] = {}
3config['database']['host'] = 'localhost'
4config['database']['port'] = 5432
5config['database']['credentials'] = {
6    'user': 'admin',
7    'password': 'secret'
8}
9
10config['cache'] = {
11    'backend': 'redis',
12    'ttl': 300
13}
14
15print(config)
16# {'database': {'host': 'localhost', 'port': 5432,
17#   'credentials': {'user': 'admin', 'password': 'secret'}},
18#  'cache': {'backend': 'redis', 'ttl': 300}}

This approach requires creating intermediate dicts manually. Assigning config['database']['host'] fails with a KeyError if config['database'] does not exist yet.

Method 3: defaultdict for Auto-Vivification

collections.defaultdict automatically creates missing keys, eliminating the need to pre-initialize intermediate dicts:

python
1from collections import defaultdict
2
3# One level of nesting
4nested = defaultdict(dict)
5nested['alice']['age'] = 30
6nested['alice']['email'] = '[email protected]'
7nested['bob']['age'] = 25
8
9print(dict(nested))
10# {'alice': {'age': 30, 'email': '[email protected]'}, 'bob': {'age': 25}}

For arbitrary depth nesting, define a recursive defaultdict:

python
1from collections import defaultdict
2
3def nested_dict():
4    return defaultdict(nested_dict)
5
6data = nested_dict()
7data['level1']['level2']['level3']['value'] = 42
8
9# Access works at any depth without pre-creating keys
10print(data['level1']['level2']['level3']['value'])  # 42

To convert back to regular dicts (e.g., for JSON serialization):

python
1import json
2
3def defaultdict_to_dict(d):
4    if isinstance(d, defaultdict):
5        return {k: defaultdict_to_dict(v) for k, v in d.items()}
6    return d
7
8regular = defaultdict_to_dict(data)
9print(json.dumps(regular, indent=2))

Method 4: dict.setdefault

setdefault creates a key with a default value if it does not exist, then returns the value:

python
1data = {}
2
3# setdefault returns the existing value if key exists,
4# or sets and returns the default if it doesn't
5data.setdefault('users', {}).setdefault('alice', {})['age'] = 30
6data.setdefault('users', {}).setdefault('alice', {})['email'] = '[email protected]'
7
8print(data)
9# {'users': {'alice': {'age': 30, 'email': '[email protected]'}}}

This is verbose but works without importing anything.

Method 5: Dictionary Comprehension

Build nested dicts from existing data:

python
1# From a list of records
2records = [
3    ('alice', 'age', 30),
4    ('alice', 'email', '[email protected]'),
5    ('bob', 'age', 25),
6    ('bob', 'email', '[email protected]'),
7]
8
9result = {}
10for name, key, value in records:
11    result.setdefault(name, {})[key] = value
12
13print(result)
14# {'alice': {'age': 30, 'email': '[email protected]'},
15#  'bob': {'age': 25, 'email': '[email protected]'}}
16
17# From two related dicts
18names = {'a': 'Alice', 'b': 'Bob'}
19ages = {'a': 30, 'b': 25}
20
21combined = {k: {'name': names[k], 'age': ages[k]} for k in names}
22print(combined)
23# {'a': {'name': 'Alice', 'age': 30}, 'b': {'name': 'Bob', 'age': 25}}

Accessing and Modifying Nested Dicts

python
1users = {
2    'alice': {'age': 30, 'scores': [90, 85, 92]},
3    'bob': {'age': 25, 'scores': [78, 88]}
4}
5
6# Safe access with .get() to avoid KeyError
7email = users.get('alice', {}).get('email', 'not set')
8print(email)  # 'not set'
9
10# Deep get helper
11def deep_get(d, keys, default=None):
12    for key in keys:
13        if isinstance(d, dict):
14            d = d.get(key, default)
15        else:
16            return default
17    return d
18
19print(deep_get(users, ['alice', 'scores']))  # [90, 85, 92]
20print(deep_get(users, ['charlie', 'age'], 0))  # 0
21
22# Update nested value
23users['alice']['scores'].append(95)

Merging Nested Dicts

Python's dict.update() and {**a, **b} only merge at the top level. For deep merging:

python
1def deep_merge(base, override):
2    result = base.copy()
3    for key, value in override.items():
4        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
5            result[key] = deep_merge(result[key], value)
6        else:
7            result[key] = value
8    return result
9
10defaults = {'db': {'host': 'localhost', 'port': 5432, 'pool': 5}}
11overrides = {'db': {'host': 'prod-server', 'ssl': True}}
12
13config = deep_merge(defaults, overrides)
14print(config)
15# {'db': {'host': 'prod-server', 'port': 5432, 'pool': 5, 'ssl': True}}

Common Pitfalls

  • KeyError on missing intermediate keys: data['a']['b'] = 1 fails if data['a'] does not exist. Use defaultdict, setdefault, or check with if 'a' not in data: data['a'] = {} first.
  • Mutable default arguments: Never use def f(d={}) — the same dict is shared across all calls. Use def f(d=None): d = d or {} instead.
  • Shallow copy trap: copy() and {**d} only copy the top level. Nested dicts still share references. Use copy.deepcopy() for independent copies.
  • defaultdict creates keys on read: Accessing d['missing'] on a defaultdict creates the key with the default value. This can silently grow the dict. Use .get() for read-only access or convert to a regular dict when done building.
  • JSON compatibility: defaultdict is not directly JSON-serializable. Convert to regular dict with dict(d) (shallow) or a recursive converter before calling json.dumps.

Summary

  • Use literal syntax {'key': {'nested': 'value'}} for static, known structures
  • Use defaultdict(dict) for one-level auto-vivification when building dicts incrementally
  • Use a recursive defaultdict factory for arbitrary-depth nesting without pre-initialization
  • Use dict.setdefault() for occasional nested key creation without imports
  • Use copy.deepcopy() when you need an independent copy of a nested dict
  • Always use .get() with defaults for safe access to potentially missing nested keys

Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms