dictionary
programming
tutorial
Python
data structures

How to create key or append an element to key?

Master System Design with Codemia

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

Introduction

When building a dictionary where values are lists, you need to handle the case where a key does not yet exist. Appending to a missing key raises KeyError. The cleanest solutions are collections.defaultdict(list), which auto-creates empty lists for new keys, and dict.setdefault(key, []).append(value), which initializes and appends in one call. Both eliminate the manual "check if key exists, create list if not, then append" pattern.

The Problem

python
data = {}
data["fruits"].append("apple")
# KeyError: 'fruits'

The key "fruits" does not exist, so there is no list to append to.

python
1from collections import defaultdict
2
3data = defaultdict(list)
4
5data["fruits"].append("apple")
6data["fruits"].append("banana")
7data["vegetables"].append("carrot")
8data["fruits"].append("cherry")
9
10print(dict(data))
11# {'fruits': ['apple', 'banana', 'cherry'], 'vegetables': ['carrot']}

When you access data["fruits"] and it does not exist, defaultdict calls list() to create an empty list, stores it, and returns it. The .append() then works normally.

Method 2: dict.setdefault()

python
1data = {}
2
3data.setdefault("fruits", []).append("apple")
4data.setdefault("fruits", []).append("banana")
5data.setdefault("vegetables", []).append("carrot")
6
7print(data)
8# {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}

setdefault(key, default) returns the existing value if the key exists, or sets and returns the default if it does not. Chaining .append() adds to the returned list.

Method 3: Manual Check

python
1data = {}
2key = "fruits"
3value = "apple"
4
5if key not in data:
6    data[key] = []
7data[key].append(value)

Explicit but verbose. Use defaultdict or setdefault instead.

Method 4: Try/Except

python
1data = {}
2
3try:
4    data["fruits"].append("apple")
5except KeyError:
6    data["fruits"] = ["apple"]

This follows Python's "easier to ask forgiveness than permission" (EAFP) principle, but it is clumsier than setdefault for this use case.

Comparison

python
1from collections import defaultdict
2
3items = [("fruit", "apple"), ("fruit", "banana"), ("veggie", "carrot"),
4         ("fruit", "cherry"), ("veggie", "pea")]
5
6# defaultdict - cleanest
7d1 = defaultdict(list)
8for k, v in items:
9    d1[k].append(v)
10
11# setdefault - no import needed
12d2 = {}
13for k, v in items:
14    d2.setdefault(k, []).append(v)
15
16# Manual check - most explicit
17d3 = {}
18for k, v in items:
19    if k not in d3:
20        d3[k] = []
21    d3[k].append(v)
22
23# All produce: {'fruit': ['apple', 'banana', 'cherry'], 'veggie': ['carrot', 'pea']}

Creating Key or Incrementing a Counter

The same pattern applies to counters:

python
1from collections import defaultdict
2
3# Counting occurrences
4counter = defaultdict(int)
5words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
6
7for word in words:
8    counter[word] += 1
9
10print(dict(counter))
11# {'apple': 3, 'banana': 2, 'cherry': 1}
12
13# Or use collections.Counter directly
14from collections import Counter
15counter = Counter(words)

Creating Key or Adding to a Set

python
1from collections import defaultdict
2
3# Unique values per key
4data = defaultdict(set)
5data["fruits"].add("apple")
6data["fruits"].add("banana")
7data["fruits"].add("apple")  # Duplicate - ignored
8
9print(dict(data))
10# {'fruits': {'apple', 'banana'}}

Real-World Example: Grouping Records

python
1from collections import defaultdict
2
3students = [
4    {"name": "Alice", "grade": "A"},
5    {"name": "Bob", "grade": "B"},
6    {"name": "Charlie", "grade": "A"},
7    {"name": "Diana", "grade": "C"},
8    {"name": "Eve", "grade": "B"},
9]
10
11by_grade = defaultdict(list)
12for student in students:
13    by_grade[student["grade"]].append(student["name"])
14
15print(dict(by_grade))
16# {'A': ['Alice', 'Charlie'], 'B': ['Bob', 'Eve'], 'C': ['Diana']}

Nested defaultdict

python
1from collections import defaultdict
2
3# Two-level grouping
4sales = defaultdict(lambda: defaultdict(list))
5
6sales["2025"]["Q1"].append(1000)
7sales["2025"]["Q1"].append(1200)
8sales["2025"]["Q2"].append(1500)
9sales["2024"]["Q4"].append(900)
10
11print(sales["2025"]["Q1"])  # [1000, 1200]

Other Languages

JavaScript

javascript
1const data = {};
2const key = "fruits";
3
4// Using logical OR
5(data[key] = data[key] || []).push("apple");
6
7// Using nullish coalescing
8(data[key] ??= []).push("apple");

Java

java
1Map<String, List<String>> data = new HashMap<>();
2
3// computeIfAbsent - creates list if key is absent
4data.computeIfAbsent("fruits", k -> new ArrayList<>()).add("apple");
5data.computeIfAbsent("fruits", k -> new ArrayList<>()).add("banana");

Ruby

ruby
data = Hash.new { |h, k| h[k] = [] }
data["fruits"] << "apple"
data["fruits"] << "banana"

Common Pitfalls

  • dict.fromkeys(keys, []) shares one list: All keys point to the same list object. Use {k: [] for k in keys} instead to create independent lists.
  • **defaultdict(list) vs defaultdict([])**: defaultdicttakes a callable.listis callable (creates[]). []is not callable - raisesTypeError`.
  • Accessing defaultdict creates keys: if key in d is safe, but d[key] creates the key with a default value even if you only wanted to check. Use key in d for existence checks.
  • JSON serialization: json.dumps() works with defaultdict, but the default factory is lost. Convert to dict() first if round-tripping matters.
  • Thread safety: Neither defaultdict nor setdefault is atomic. In multi-threaded code, use threading.Lock or concurrent.futures.

Summary

  • Use defaultdict(list) for the cleanest auto-creating dictionary of lists
  • Use dict.setdefault(key, []).append(value) when you want a plain dict with no imports
  • Never use dict.fromkeys(keys, []) - all keys share the same list object
  • The pattern works with set, int, or any callable as the default factory
  • In JavaScript use ??=, in Java use computeIfAbsent, in Ruby use Hash.new with a block

Course illustration
Course illustration

All Rights Reserved.