Python
Nested Dictionaries
Key Access
Data Structures
Programming Tips

Access nested dictionary items via a list of keys?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Accessing nested dictionary values from a list of keys is a common Python task when working with JSON-like data, configuration trees, or API payloads. The main idea is to walk the structure one step at a time until you either reach the final value or discover that the path is invalid.

The simple version is only a loop. The design question is how you want failures to behave: raise an error immediately, return a default, or support mixed dictionary-and-list paths.

The Basic Dictionary-Only Version

If every step is guaranteed to exist and every intermediate value is a dictionary, a short loop is enough.

python
1def get_in(data, keys):
2    current = data
3    for key in keys:
4        current = current[key]
5    return current
6
7
8payload = {
9    "user": {
10        "profile": {
11            "email": "[email protected]"
12        }
13    }
14}
15
16print(get_in(payload, ["user", "profile", "email"]))

This is fine when missing keys should be treated as real errors.

A Safe Version With a Default Value

In many applications, missing paths are expected. In that case, returning a default is often more useful than raising KeyError.

python
1def get_in_safe(data, keys, default=None):
2    current = data
3    for key in keys:
4        if not isinstance(current, dict) or key not in current:
5            return default
6        current = current[key]
7    return current
8
9
10print(get_in_safe(payload, ["user", "profile", "phone"], default="N/A"))

This version is safer when the input structure is external or partially optional.

Supporting Lists in the Path

Real payloads often mix dictionaries and lists. For example, a path may look like users, then index 1, then key name.

python
1def get_path(obj, path, default=None):
2    current = obj
3    for step in path:
4        if isinstance(step, int):
5            if not isinstance(current, list) or step < 0 or step >= len(current):
6                return default
7            current = current[step]
8        else:
9            if not isinstance(current, dict) or step not in current:
10                return default
11            current = current[step]
12    return current
13
14
15payload = {
16    "users": [
17        {"name": "A"},
18        {"name": "B"}
19    ]
20}
21
22print(get_path(payload, ["users", 1, "name"]))

This is often the most practical general-purpose helper for nested JSON-like structures.

When Raising an Error Is Better

Returning a default is convenient, but sometimes it hides real schema bugs. If the data shape should be strict, letting the lookup fail can be the right design.

That is especially true when:

  • the path is part of an internal contract
  • missing data indicates a programming bug
  • silent fallback would mask corrupted input

So the “safe” version is not always the best version. It depends on how trustworthy the data is supposed to be.

Use a Sentinel When None Is Ambiguous

If None is a legitimate stored value, returning None for “missing path” is ambiguous. In that case, use a sentinel object.

python
1_MISSING = object()
2
3result = get_in_safe(payload, ["user", "profile", "phone"], default=_MISSING)
4if result is _MISSING:
5    print("path missing")

This keeps “missing” distinct from “present but value is None.”

Why Chained .get(...) Calls Are Limited

People sometimes try to solve the problem with chained dict.get calls.

python
email = payload.get("user", {}).get("profile", {}).get("email")

This works for short, dictionary-only paths, but it becomes awkward when:

  • the path length is dynamic
  • lists appear in the structure
  • you want consistent error or default handling

A loop-based helper is usually the cleaner abstraction.

Common Pitfalls

A common mistake is assuming every intermediate value is a dictionary. In mixed data, one step may be a list or None.

Another mistake is returning None for both missing paths and legitimate None values without distinguishing the cases.

Developers also sometimes overuse chained .get(...) calls when the path is dynamic and would be much clearer as a reusable helper.

Finally, decide deliberately whether a missing path should raise or quietly return a default. That is a design choice, not just a syntax choice.

Summary

  • Walking a nested path is just repeated access through dictionaries or lists.
  • Use a simple loop when missing keys should raise an error.
  • Use a safe helper with a default when the input structure may be incomplete.
  • Support integer path steps if the data can contain lists.
  • Distinguish “missing” from “present but None” when that difference matters to the caller.

Course illustration
Course illustration

All Rights Reserved.