Cloud Tech by Victor
BackendBeginner

Python Data Structures

When to reach for a list, tuple, dict, or set based on what operations you actually need fast, and why choosing the wrong one is a common hidden performance bug.

Updated 2026-07-223 min read

Overview

Python's built-in collection types, list, tuple, dict, and set, look interchangeable at a glance but have genuinely different performance characteristics driven by how they're implemented internally. Lists and tuples are ordered sequences backed by contiguous arrays; dicts and sets are hash tables. That implementation difference is what makes membership checks and key lookups fast on a dict/set and slow on a list/tuple, and it's the single most common hidden performance bug in Python code, using a list where a set or dict would make an O(n) operation O(1).

Quick Reference

TypeOrderedMutableMembership checkTypical use
listYesYesO(n)Ordered, changeable collection
tupleYesNoO(n)Fixed-size, heterogeneous grouping
dictYes (insertion order)YesO(1) averageKey-value lookup
setNoYesO(1) averageFast membership checks, deduplication

Syntax

python
users = ["alice", "bob", "carol"]           # list
point = (3.0, 4.0)                            # tuple
config = {"debug": True, "workers": 4}         # dict
seen_ids = {101, 102, 103}                     # set

Examples

python
# Slow: O(n) membership check against a growing list, repeated
# for every item - O(n * m) overall for m checks.
allowed = ["free", "pro", "enterprise"]
if plan in allowed:  # fine for 3 items, a real cost at scale
    ...

# Fast: O(1) average membership check against a set
allowed = {"free", "pro", "enterprise"}
if plan in allowed:
    ...

Reach for a set the moment you're checking membership repeatedly

Any code doing x in collection inside a loop, against a collection that isn't already a set or dict, is a strong signal to convert it; the fix is usually a one-line change with an immediate, measurable speedup.

Visual Diagram

Common Mistakes

  • Using a list for repeated membership checks (in) when a set would turn an O(n) scan into an O(1) average-case lookup.
  • Trying to use a list as a dictionary key or set member and being surprised by the TypeError: unhashable type, reach for a tuple instead if the collection is fixed-size.
  • Using a list where insertion order genuinely doesn't matter and only uniqueness/fast lookup does, a set communicates that intent and performs better.
  • Mutating a list while iterating over it directly, which silently skips or duplicates elements because the indices shift underneath the iteration.

Performance

  • Membership checks: O(1) average for set/dict, O(n) for list/tuple; this is the single highest-leverage data-structure choice in typical Python code.
  • Appending to the end of a list is amortized O(1); inserting at the beginning (list.insert(0, x)) is O(n) because every subsequent element has to shift, a collections.deque is the right structure for frequent front-insertion.
  • Dict and set operations degrade toward O(n) worst case under pathological hash collisions, but Python's hash implementation makes this a non-issue for typical real-world keys.

Best Practices

  • Default to a set for any collection whose primary use is membership testing or deduplication, not a list.
  • Use tuples for fixed-size, heterogeneous data (coordinates, database rows) and lists for variable-length, homogeneous collections.
  • Reach for collections.deque instead of a list when a collection needs fast insertion/removal at both ends, not just the end.
  • Profile before assuming a data-structure change matters for a specific hot path; the complexity difference is real, but it only shows up meaningfully at real scale.

Interview questions

Why is checking membership with `in` fast on a set or dict but slow on a list?

Sets and dicts are implemented as hash tables, checking whether a value exists means computing its hash and looking up that bucket directly, an O(1) average-case operation regardless of how many items are stored. A list has no such index; checking membership means scanning entries one by one until a match is found or the list ends, an O(n) operation that gets slower as the list grows. For any code doing repeated membership checks against a growing collection, using a set instead of a list is often the single biggest easy performance fix available.

What is the practical difference between a list and a tuple, beyond mutability?

The most visible difference is that lists are mutable (items can be added, removed, or changed after creation) and tuples are immutable (fixed once created). That immutability has real consequences: tuples can be used as dictionary keys or set members because they're hashable, while lists cannot. Tuples also communicate intent, a fixed-size, heterogeneous grouping (like a coordinate pair) is usually a better fit for a tuple, while a variable-length, homogeneous collection is usually a better fit for a list, independent of whether mutation is actually needed.

Why can a list not be used as a dictionary key, but a tuple can?

Dictionary keys must be hashable, and hashability requires that an object's hash value never changes for the lifetime of the object, which in turn requires immutability, because a mutable object's contents (and therefore its logical value) could change after being used as a key, silently breaking the hash table's internal bucket placement. Lists are mutable, so Python makes them explicitly unhashable to prevent that class of bug. Tuples are immutable, so as long as every element they contain is also hashable, the tuple itself is hashable and safe to use as a dictionary key or set member.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement