Python lists versus dictionaries in a reporting task
In this article (6 sections)
A list preserves an ordered collection of records, including repeated values. A dictionary associates keys with values for keyed access. The choice affects what information survives: building a dictionary from nonunique business keys can silently replace earlier records.
Choose the structure from the task's grain and access pattern. A dictionary is not a universal “cleaner” version of a list.
Preserve incoming records before resolving duplicates
Consider three synthetic deliveries:
records = [
{"order_id": "R1", "amount": "19.00"},
{"order_id": "R2", "amount": "5.00"},
{"order_id": "R1", "amount": "20.00"},
]
index = {row["order_id"]: row for row in records}
assert len(records) == 3
assert len(index) == 2
assert index["R1"]["amount"] == "20.00"
print("The dictionary retained the later R1 payload")The last R1 value replaces the earlier one under the same key. Python has not determined that 20.00 is authoritative; the code has merely selected the later list entry through assignment order.
If the records represent conflicting versions, you need a source-defined version rule or an exception. If they are identical replays, a documented deduplication rule may be appropriate. The container alone cannot decide.
Build a checked index
For a table that promises one row per order, fail when the key repeats rather than silently overwriting it:
def unique_index(rows, key):
result = {}
for row in rows:
value = row[key]
if value in result:
raise ValueError(f"duplicate key: {value}")
result[value] = row
return result
rows = [{"order_id": "R1"}, {"order_id": "R2"}]
assert set(unique_index(rows, "order_id")) == {"R1", "R2"}
try:
unique_index(rows + [{"order_id": "R1"}], "order_id")
except ValueError as error:
assert str(error) == "duplicate key: R1"
else:
raise AssertionError("Duplicate should have failed")This contract rejects all repeated keys. The full reporting lab uses a more specific policy: identical replays are counted separately, while conflicting payloads fail the batch.
Use a dictionary of lists for a one-to-many relationship
A customer can have several orders, so indexing directly by customer ID would lose orders. Group into lists instead:
from collections import defaultdict
orders = [
{"order_id": "R1", "customer_id": "0012"},
{"order_id": "R2", "customer_id": "0012"},
{"order_id": "R3", "customer_id": "0042"},
]
by_customer = defaultdict(list)
for order in orders:
by_customer[order["customer_id"]].append(order)
assert len(by_customer["0012"]) == 2
assert sum(len(group) for group in by_customer.values()) == len(orders)
print(dict(by_customer))The grouped structure preserves all three records while supporting customer lookup. The final assertion checks that grouping did not lose or multiply records.
Distinguish order from sorting
A list's current order may reflect source delivery, not business chronology. A dictionary preserves insertion order in current Python, but that still does not make its iteration order a date sort or an authoritative version sequence.
Sort explicitly using the intended date and a deterministic tie-breaker when the output requires chronological order. Preserve the original sequence if it matters for audit.
Python's data-structures tutorial describes the containers and their operations. The business key and duplicate policy determine their safe use in a report.
Watch for shared mutable records
The dictionary values in these examples reference the same row dictionaries supplied in the input list. Mutating a nested row through one structure can affect what is observed through the other.
Decide whether the reporting stage should treat parsed records as immutable, make a deliberate copy or construct a separate output record. The full lab uses frozen Order dataclasses for accepted records to make accidental field mutation less convenient.
Exercise: group a list of invoice lines by InvoiceID, then build a separate unique index by LineID. Explain why InvoiceID can legitimately repeat while LineID must satisfy a uniqueness contract.
NeuraPath's Data Analytics with Generative AI course connects Python containers with data grain. A correct structure preserves the relationships your report needs instead of hiding duplicate-key decisions inside a comprehension.
Continue learning
This article is part of the Python foundations for analysts sequence. Use the neighbouring tasks when you need the prerequisite or the next application.
- Review the prerequisite or neighbouring task in Python environments: reproduce an analysis on another laptop.
- Continue with Write a Python function with a clear input contract.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI