Skip to main content

How to Flatten and Remove Keys from a Nested Dictionary in Python

When working with deeply nested dictionaries, common in JSON API responses, configuration files, and NoSQL databases, you often need to flatten the structure into a single-level dictionary with compound keys. This makes the data easier to search, filter, export to tabular formats, or store in flat key-value stores.

For example:

  • Input: {'a': {'b': 1, 'c': {'d': 2}}, 'e': 3}
  • Output: {'a.b': 1, 'a.c.d': 2, 'e': 3}

Each nested key is joined with its parent using a dot separator (.), producing a flat dictionary with no nesting.

This guide covers multiple approaches to flatten nested dictionaries in Python.

The most intuitive approach uses a recursive function that traverses the nested structure and builds compound keys as it descends:

def flatten_dict(d, parent_key="", separator="."):
"""Recursively flatten a nested dictionary."""
items = {}
for key, value in d.items():
new_key = f"{parent_key}{separator}{key}" if parent_key else key
if isinstance(value, dict):
items.update(flatten_dict(value, new_key, separator))
else:
items[new_key] = value
return items

nested = {'a': {'b': 1, 'c': {'d': 2}}, 'e': 3}
flat = flatten_dict(nested)
print(flat)

Output:

{'a.b': 1, 'a.c.d': 2, 'e': 3}

How It Works​

  1. For each key-value pair, a compound key is built by joining the parent key with the current key using the separator.
  2. If the value is a dict, the function recurses deeper.
  3. If the value is a non-dict (leaf node), it's added to the result with its full compound key.
Customizable separator

You can change the separator to match your needs:

print(flatten_dict(nested, separator="/"))
# Output: {'a/b': 1, 'a/c/d': 2, 'e': 3}

print(flatten_dict(nested, separator="_"))
# Output: {'a_b': 1, 'a_c_d': 2, 'e': 3}

Using a Stack (Iterative Depth-First)​

For very deeply nested dictionaries where recursion might hit Python's recursion limit, an iterative approach using a stack performs depth-first traversal without recursive calls:

def flatten_dict(d, separator="."):
"""Flatten a nested dictionary using an iterative stack."""
flat = {}
stack = [(d, "")]

while stack:
current, parent_key = stack.pop()
for key, value in current.items():
new_key = f"{parent_key}{separator}{key}" if parent_key else key
if isinstance(value, dict):
stack.append((value, new_key))
else:
flat[new_key] = value

return flat

nested = {'a': {'b': 1, 'c': {'d': 2}}, 'e': 3}
print(flatten_dict(nested))

Output:

{'e': 3, 'a.b': 1, 'a.c.d': 2}
note

The output key order may differ from the recursive approach because a stack processes items in LIFO (last-in, first-out) order. The data itself is identical - only the iteration order changes. If order matters, you can use dict(sorted(flat.items())).

Using a Queue (Iterative Breadth-First)​

A queue-based approach processes the dictionary level by level (breadth-first), which can be useful when you want to process shallower keys before deeper ones:

from collections import deque

def flatten_dict(d, separator="."):
"""Flatten a nested dictionary using breadth-first traversal."""
flat = {}
queue = deque([(d, "")])

while queue:
current, parent_key = queue.popleft()
for key, value in current.items():
new_key = f"{parent_key}{separator}{key}" if parent_key else key
if isinstance(value, dict):
queue.append((value, new_key))
else:
flat[new_key] = value

return flat

nested = {'a': {'b': 1, 'c': {'d': 2}}, 'e': 3}
print(flatten_dict(nested))

Output:

{'e': 3, 'a.b': 1, 'a.c.d': 2}

The queue processes top-level keys ('a', 'e') before diving into nested levels ('a.b', 'a.c', then 'a.c.d').

Flattening with Specific Key Removal​

Sometimes you need to flatten the dictionary and remove specific keys or values during the process. Here's how to combine flattening with filtering:

def flatten_and_remove(d, remove_keys=None, separator="."):
"""Flatten a nested dictionary and remove specified keys."""
if remove_keys is None:
remove_keys = set()

flat = {}
stack = [(d, "")]

while stack:
current, parent_key = stack.pop()
for key, value in current.items():
if key in remove_keys:
continue # Skip this key entirely
new_key = f"{parent_key}{separator}{key}" if parent_key else key
if isinstance(value, dict):
stack.append((value, new_key))
else:
flat[new_key] = value

return flat

nested = {
'user': {
'name': 'Alice',
'password': 'secret123', # Sensitive (remove)
'profile': {
'age': 30,
'email': 'alice@example.com',
'internal_id': 'xyz' # Internal (remove)
}
},
'status': 'active'
}

result = flatten_and_remove(nested, remove_keys={'password', 'internal_id'})
for key, value in sorted(result.items()):
print(f"{key}: {value}")

Output:

status: active
user.name: Alice
user.profile.age: 30
user.profile.email: alice@example.com

The password and internal_id keys are excluded from the flattened result.

Handling Lists Inside Nested Dictionaries​

Real-world JSON data often contains lists alongside nested dicts. Here's how to handle them:

def flatten_dict(d, parent_key="", separator="."):
"""Flatten a nested dictionary, including lists."""
flat = {}

if isinstance(d, dict):
for key, value in d.items():
new_key = f"{parent_key}{separator}{key}" if parent_key else key
flat.update(flatten_dict(value, new_key, separator))
elif isinstance(d, list):
for index, item in enumerate(d):
new_key = f"{parent_key}{separator}{index}" if parent_key else str(index)
flat.update(flatten_dict(item, new_key, separator))
else:
flat[parent_key] = d

return flat

nested = {
'user': 'Alice',
'scores': [95, 87, 92],
'address': {
'city': 'NYC',
'phones': ['555-0100', '555-0200']
}
}

result = flatten_dict(nested)
for key, value in sorted(result.items()):
print(f"{key}: {value}")

Output:

address.city: NYC
address.phones.0: 555-0100
address.phones.1: 555-0200
scores.0: 95
scores.1: 87
scores.2: 92
user: Alice
Empty dicts and lists are dropped

With all the approaches shown above, empty dictionaries ({}) and empty lists ([]) produce no keys in the output and are silently dropped. If you need to preserve them, add explicit handling:

if isinstance(d, dict):
if not d:
flat[parent_key] = {} # Preserve empty dict
for key, value in d.items():
# ... rest of logic

Unflattening: Converting Back to Nested​

If you need to reverse the flattening and reconstruct the nested structure:

def unflatten_dict(flat, separator="."):
"""Convert a flat dictionary back to a nested dictionary."""
nested = {}
for compound_key, value in flat.items():
keys = compound_key.split(separator)
current = nested
for key in keys[:-1]:
current = current.setdefault(key, {})
current[keys[-1]] = value
return nested

flat = {'a.b': 1, 'a.c.d': 2, 'e': 3}
nested = unflatten_dict(flat)
print(nested)

Output:

{'a': {'b': 1, 'c': {'d': 2}}, 'e': 3}

Comparison of Approaches​

MethodTraversal OrderHandles Deep NestingBest For
RecursionDepth-first⚠️ Limited by recursion depthMost use cases (recommended)
Stack (iterative)Depth-first✅ No recursion limitVery deeply nested structures
Queue (iterative)Breadth-first✅ No recursion limitLevel-by-level processing

Conclusion​

Flattening nested dictionaries is essential when working with complex JSON data or configuration files.

  • The recursive approach is the most readable and works well for typical nesting depths.
  • For extremely deep structures, switch to the iterative stack-based approach to avoid hitting Python's recursion limit.

All methods support customizable separators and can be extended to handle lists, key filtering, and empty value preservation as needed.