Skip to content

Choosing the Right Structure

The right data structure turns an O(n²) algorithm into O(n). This page is a reference you can return to when in doubt.

Do you need key → value lookup?
└─ Yes → dict
├─ Keys might be missing and you want a safe default? → defaultdict
└─ You're counting occurrences? → Counter
Do you need ordered, changeable items?
└─ Yes → list
└─ Fast append/pop from the left too? → deque (with maxlen for sliding windows)
Do you need ordered, fixed items (won't change)?
└─ Yes → tuple
└─ Fields have names? → namedtuple (or @dataclass if you need methods)
Do you need uniqueness / set algebra?
└─ Yes → set
└─ Must be hashable (e.g. dict key)? → frozenset
Operationlistdequedictset
Access by index [i]O(1)O(n)O(1)
x in sO(n)O(n)O(1)O(1)
Append to endO(1)O(1)
Append to frontO(n)O(1)
Insert middleO(n)O(n)
Delete by valueO(n)O(n)O(1)O(1)
IterationO(n)O(n)O(n)O(n)
Union / intersectionO(n²)O(n)

Using a list for membership tests in a hot loop

# Slow — O(n) per check, O(n²) total
valid = [10, 20, 30, 40, 50]
hits = [x for x in big_list if x in valid]
# Fast — O(1) per check, O(n) total
valid = {10, 20, 30, 40, 50}
hits = [x for x in big_list if x in valid]

Using list.pop(0) as a queue

# Slow — O(n) per pop
queue = [1, 2, 3, 4]
queue.pop(0)
# Fast — O(1) per popleft
from collections import deque
queue = deque([1, 2, 3, 4])
queue.popleft()

Manually tracking counts with a dict

# Verbose
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
# Idiomatic
from collections import Counter
counts = Counter(words)
If you need…Use
Ordered, mutable sequencelist
Fast front-and-back append/popdeque
Ordered, immutable sequencetuple
Immutable sequence with named fieldsnamedtuple
Key → value, any keysdict
Key → value, missing keys safedefaultdict
Counting occurrencesCounter
Unique items, O(1) membershipset
Unique items, must be hashablefrozenset