Python
dictionaries
default values
programming
coding

Dictionaries and default values

Master System Design with Codemia

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

Introduction

When accessing a dictionary key that might not exist, Python raises KeyError by default. To avoid this, you can use dict.get() for a fallback value, collections.defaultdict for automatic defaults, dict.setdefault() to set-and-return, or the | merge operator to overlay defaults onto user input. Each method serves a different use case — get() for read-only access, defaultdict for building up values, and setdefault() for one-time initialization.

The Problem: KeyError

python
1config = {"host": "localhost", "port": 5432}
2
3print(config["timeout"])
4# KeyError: 'timeout'

Accessing a missing key crashes the program. Default values prevent this.

Method 1: dict.get() (Most Common)

python
1config = {"host": "localhost", "port": 5432}
2
3# Returns the default if key is missing
4timeout = config.get("timeout", 30)
5print(timeout)  # 30
6
7# Returns None if no default specified
8debug = config.get("debug")
9print(debug)  # None
10
11# Key exists — returns actual value
12host = config.get("host", "0.0.0.0")
13print(host)  # "localhost"

get() never modifies the dictionary. It is a read-only operation.

Method 2: collections.defaultdict

python
1from collections import defaultdict
2
3# Default factory for missing keys
4counts = defaultdict(int)      # Missing keys return 0
5counts["apple"] += 1
6counts["banana"] += 3
7counts["apple"] += 2
8print(dict(counts))  # {'apple': 3, 'banana': 3}
9
10# List factory
11groups = defaultdict(list)
12groups["fruits"].append("apple")
13groups["fruits"].append("banana")
14print(dict(groups))  # {'fruits': ['apple', 'banana']}
15
16# Custom factory
17settings = defaultdict(lambda: "not set")
18settings["name"] = "MyApp"
19print(settings["name"])     # "MyApp"
20print(settings["version"])  # "not set"

defaultdict calls the factory function whenever a missing key is accessed, and stores the result in the dictionary.

Method 3: dict.setdefault()

python
1config = {}
2
3# Sets and returns the default if key is missing
4config.setdefault("timeout", 30)
5print(config)  # {'timeout': 30}
6
7# Does nothing if key already exists
8config.setdefault("timeout", 60)
9print(config)  # {'timeout': 30} — not overwritten

setdefault() modifies the dictionary (unlike get()). Use it when you want to initialize a key only on first access.

Method 4: Merge Operator for Defaults (Python 3.9+)

python
1defaults = {"timeout": 30, "retries": 3, "debug": False}
2user_config = {"timeout": 60, "debug": True}
3
4# Defaults first, user overrides second
5config = defaults | user_config
6print(config)
7# {'timeout': 60, 'retries': 3, 'debug': True}
8
9# Pre-3.9 equivalent
10config = {**defaults, **user_config}

The right-hand dictionary wins on duplicate keys, so put defaults on the left and overrides on the right.

Method 5: try/except

python
1config = {"host": "localhost"}
2
3try:
4    timeout = config["timeout"]
5except KeyError:
6    timeout = 30
7
8print(timeout)  # 30

This follows the EAFP (Easier to Ask Forgiveness than Permission) pattern. Use it when you need to handle missing keys differently from providing a default.

Method 6: The in Operator

python
1config = {"host": "localhost"}
2
3if "timeout" in config:
4    timeout = config["timeout"]
5else:
6    timeout = 30

This LBYL (Look Before You Leap) approach is explicit but verbose. get() is preferred for simple default values.

Nested Dictionary Defaults

python
1# Deep access with chained get()
2data = {"user": {"name": "Alice"}}
3
4email = data.get("user", {}).get("email", "[email protected]")
5print(email)  # "[email protected]"
6
7# With a helper function
8def deep_get(d, *keys, default=None):
9    for key in keys:
10        if isinstance(d, dict):
11            d = d.get(key)
12        else:
13            return default
14    return d if d is not None else default
15
16print(deep_get(data, "user", "name"))                # "Alice"
17print(deep_get(data, "user", "email", default="N/A")) # "N/A"
18print(deep_get(data, "settings", "theme", default="dark")) # "dark"

Pattern: Configuration with Defaults

python
1def connect_db(**kwargs):
2    defaults = {
3        "host": "localhost",
4        "port": 5432,
5        "database": "myapp",
6        "timeout": 30,
7        "pool_size": 5,
8    }
9    config = {**defaults, **kwargs}
10
11    print(f"Connecting to {config['host']}:{config['port']}/{config['database']}")
12    print(f"Timeout: {config['timeout']}s, Pool: {config['pool_size']}")
13
14connect_db(host="prod-db.example.com", pool_size=20)
15# Connecting to prod-db.example.com:5432/myapp
16# Timeout: 30s, Pool: 20

Common Pitfalls

  • get() with mutable default: d.get("key", []) returns a new empty list each time. If you want to build up a list, use setdefault() or defaultdict(list) instead, which stores the default in the dict.
  • defaultdict creates keys on read: Accessing d["missing"] on a defaultdict inserts the key with the default value. If you only want to check without creating, use key in d or d.get(key).
  • setdefault() evaluates the default eagerly: d.setdefault("key", expensive_function()) calls the function even if the key exists. For expensive defaults, use if key not in d: d[key] = expensive_function().
  • None vs missing: d.get("key") returns None for both {"key": None} and {}. If None is a valid value, use key in d to distinguish.
  • dict.fromkeys() shares mutable defaults: dict.fromkeys(["a", "b"], []) makes both keys point to the same list. Use {k: [] for k in keys} instead.

Summary

  • Use dict.get(key, default) for read-only access with a fallback value
  • Use defaultdict(factory) when building up collections (counting, grouping)
  • Use dict.setdefault(key, default) for one-time initialization of a key
  • Use {**defaults, **overrides} or defaults | overrides to merge configuration dicts
  • get() does not modify the dict; setdefault() and defaultdict do
  • Check key in d when None is a valid value and you need to distinguish it from a missing key

Course illustration
Course illustration

All Rights Reserved.