Skip to content

How to Count Elements in a Nested Dictionary in Python

Learn recursive methods to count leaf values, keys, and total elements in Python nested dictionaries, with examples for JSON, config files, and hierarchical data structures.

Nested dictionaries are a common data structure in Python, especially when working with JSON responses, configuration files, or hierarchical data models. Counting elements within these structures is not as straightforward as calling len(), because a single len() call only counts top-level keys and ignores everything nested beneath them.

In this guide, you will learn how to count different types of elements in nested dictionaries, including leaf values, all keys, and total nodes. Each approach uses recursion to traverse the full tree structure, and every method is explained with clear examples and output.

Why len() Is Not Enough

Before diving into recursive solutions, it is important to understand why the built-in len() function falls short with nested dictionaries:

data = {
    "a": 1,
    "b": {
        "c": 2,
        "d": 3
    }
}

print(len(data))

Output:

2

len() returns 2 because it only counts the top-level keys ("a" and "b"). It does not look inside the nested dictionary under "b", which contains two additional keys and values. To get a complete count, you need to recurse into every level of the structure.

Counting Leaf Values

Leaf values are the actual data points at the end of each branch. They exclude container objects like dictionaries and lists, counting only the final scalar values:

def count_leaves(obj):
    if isinstance(obj, dict):
        return sum(count_leaves(value) for value in obj.values())
    elif isinstance(obj, list):
        return sum(count_leaves(item) for item in obj)
    else:
        return 1

data = {
    "a": 1,
    "b": {
        "c": 2,
        "d": [3, 4, 5]
    }
}

print(count_leaves(data))

Output:

5

The five leaf values are 1, 2, 3, 4, and 5. The function recurses into dictionaries and lists but counts 1 for every non-container element it encounters.

Counting All Keys

To measure the structural complexity of a nested dictionary, you can count every key at all nesting levels:

def count_keys(obj):
    if not isinstance(obj, dict):
        return 0
    
    count = len(obj)
    for value in obj.values():
        count += count_keys(value)
    
    return count

data = {
    "user": {
        "name": "Alice",
        "address": {
            "city": "Boston",
            "zip": "02101"
        }
    }
}

print(count_keys(data))

Output:

5

The five keys are "user", "name", "address", "city", and "zip". At each level, len(obj) counts the keys in the current dictionary, and the recursive call descends into any value that is itself a dictionary.

Counting Keys and Values Together

For a complete count of every element in the structure, combine key counting and value counting into a single function:

def count_all_elements(obj):
    if isinstance(obj, dict):
        key_count = len(obj)
        nested_count = sum(count_all_elements(v) for v in obj.values())
        return key_count + nested_count
    elif isinstance(obj, list):
        return sum(count_all_elements(item) for item in obj)
    else:
        return 1

data = {"a": 1, "b": {"c": 2}}

print(count_all_elements(data))

Output:

5

The breakdown is: three keys ("a", "b", "c") plus two leaf values (1, 2) equals five total elements.

Using a Generator to Extract Leaf Values

When you need to inspect or process the actual values rather than just count them, a generator function is both flexible and memory-efficient:

def iter_leaves(obj):
    if isinstance(obj, dict):
        for value in obj.values():
            yield from iter_leaves(value)
    elif isinstance(obj, list):
        for item in obj:
            yield from iter_leaves(item)
    else:
        yield obj

data = {"a": 1, "b": {"c": [2, 3]}, "d": 4}

leaves = list(iter_leaves(data))
print(leaves)
print(len(leaves))

Output:

[1, 2, 3, 4]
4

Counting Elements by Depth Level

To understand how elements are distributed across nesting levels, count the keys at each depth:

from collections import defaultdict

def count_by_depth(obj, depth=0, counts=None):
    if counts is None:
        counts = defaultdict(int)
    
    if isinstance(obj, dict):
        counts[depth] += len(obj)
        for value in obj.values():
            count_by_depth(value, depth + 1, counts)
    
    return dict(counts)

data = {
    "level0_a": {
        "level1_a": {
            "level2_a": 1
        },
        "level1_b": 2
    },
    "level0_b": 3
}

print(count_by_depth(data))

Output:

{0: 2, 1: 2, 2: 1}

This tells you that there are 2 keys at depth 0 ("level0_a" and "level0_b"), 2 keys at depth 1 ("level1_a" and "level1_b"), and 1 key at depth 2 ("level2_a"). This kind of analysis is useful for profiling the shape of complex JSON structures or configuration hierarchies.

Handling Deep Nesting Safely

Python has a default recursion limit of 1000 frames. Most practical nested dictionaries stay well within this boundary, but deeply nested JSON data or programmatically generated structures might exceed it. You can add a depth guard to your recursive functions:

def count_leaves_safe(obj, max_depth=100):
    if max_depth <= 0:
        raise RecursionError("Maximum nesting depth exceeded")
    
    if isinstance(obj, dict):
        return sum(count_leaves_safe(v, max_depth - 1) for v in obj.values())
    elif isinstance(obj, list):
        return sum(count_leaves_safe(i, max_depth - 1) for i in obj)
    else:
        return 1

Alternatively, you can increase the recursion limit, though this should be done with caution:

import sys
sys.setrecursionlimit(2000)

A Common Mistake: Forgetting to Handle Lists

A frequent error when writing recursive functions for nested dictionaries is forgetting that values can be lists containing additional nested structures:

def count_leaves_broken(obj):
    if isinstance(obj, dict):
        return sum(count_leaves_broken(value) for value in obj.values())
    else:
        return 1

data = {"a": [1, 2, 3], "b": 4}

print(count_leaves_broken(data))

Output:

2

The function returns 2 instead of the expected 4 because it treats the list [1, 2, 3] as a single leaf value. Always include an isinstance(obj, list) check in your recursive functions to handle lists correctly:

def count_leaves_fixed(obj):
    if isinstance(obj, dict):
        return sum(count_leaves_fixed(value) for value in obj.values())
    elif isinstance(obj, list):
        return sum(count_leaves_fixed(item) for item in obj)
    else:
        return 1

data = {"a": [1, 2, 3], "b": 4}

print(count_leaves_fixed(data))

Output:

4

Method Comparison

Goal Approach What It Counts
Leaf values only Recurse, return 1 for non-containers Scalar data points
All keys Sum len(dict) at each level Structural keys at every depth
All elements Combine key and value counting Keys and leaf values together
Extract values Generator with yield from Iterable of all leaf values
Distribution by depth Track depth parameter during recursion Keys per nesting level

Conclusion

Recursion is the natural and most elegant solution for traversing tree-structured data like nested dictionaries. Choose leaf counting when you care about the actual data points stored in the structure, key counting when you want to measure structural complexity, and total element counting when you need a comprehensive view. The generator approach is ideal when you need to process or inspect the values themselves, not just count them. For deeply nested structures, always consider adding a depth guard to prevent hitting Python's recursion limit.

By combining these techniques, you can fully analyze and profile any nested dictionary structure in Python.

Navigation