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 algorithm problems with animated visualisers that show the data structure changing as the code runs.

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

Related reading
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
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.