Back to Blog
Python

Python List vs Set: Choosing the Right Data Structure

python list vs set: Compare Python lists and sets: order, duplicates, membership testing performance, memory overhead, and when to choose each.

PythonData StructuresPerformanceSet OperationsMembership Testing
Illustration comparing Python list and set structures, showing an ordered list with duplicates and an unordered set with unique elements.

python list vs set requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When deciding between a list and a set in Python, the core question is what operations you need to perform most often. Both are built-in, mutable containers, but they behave very differently under the hood. Lists preserve insertion order and allow duplicates; sets are unordered and enforce uniqueness. The choice between them often comes down to membership testing speed versus the need for positional access.

Core Differences: Order, Duplicates, and Hashability

A list is an ordered sequence of objects, accessed by index. You can append, insert, and slice, and the same value can appear multiple times. A set is an unordered collection of unique, hashable objects. It uses a hash table internally, which gives O(1) average-case membership tests but discards any notion of sequence.

fruits_list = ['apple', 'banana', 'apple', 'cherry'] fruits_set = {'apple', 'banana', 'cherry'} print(fruits_list[0]) # 'apple' print(fruits_set[0]) # TypeError: 'set' object is not subscriptable

The hashability requirement is important. Lists, dictionaries, and other mutable containers cannot be elements of a set because their hash value could change after insertion. Tuples, frozensets, strings, and numbers are fine.

Membership Testing Performance: O(1) vs O(n)

The most common reason developers switch from a list to a set is membership testing. Checking whether an item exists in a list requires a linear scan, O(n), because the list has no index. A set uses a hash table, so x in my_set averages O(1), independent of size.

items_list = list(range(100000)) items_set = set(items_list) # Membership test '99999' in items_list # scans 100,000 elements '99999' in items_set # hashes and looks up directly

For a one-off lookup the difference is negligible, but inside a loop that runs thousands of times, the difference becomes significant. If you find yourself writing if x in my_list repeatedly and the list is large, converting it to a set first is a straightforward optimization.

When a List Is the Right Choice

Use a list when order matters. If you need to iterate in insertion order, access elements by index, slice a subsequence, or store duplicate values, a list is the appropriate structure. Lists also support operations like append, extend, pop, and insert that modify the sequence at specific positions.

task_queue = [] task_queue.append('parse') task_queue.append('transform') task_queue.append('load') next_task = task_queue.pop(0) # 'parse' – FIFO behavior

Lists are also the natural choice when you need to preserve the order of input data, such as reading lines from a file or collecting user input in a sequence.

When a Set Is the Right Choice

Choose a set when uniqueness and fast membership testing are the primary requirements. Sets also provide set operations like union, intersection, and difference, which are concise and efficient for comparing collections.

valid_ids = {101, 102, 103, 104} submitted_ids = {103, 104, 105} missing = valid_ids - submitted_ids # {101, 102} common = valid_ids & submitted_ids # {103, 104}

If you need to remove duplicates from a list while preserving order, a common pattern is to use a set for tracking seen items and a list for output:

def unique_preserve_order(items): seen = set() result = [] for item in items: if item not in seen: seen.add(item) result.append(item) return result

This combines the uniqueness guarantee of a set with the order preservation of a list.

Memory Overhead and Tradeoffs

Sets generally consume more memory than lists for the same number of elements because hash tables allocate extra space to keep load factors low. For small collections the difference is trivial, but for millions of elements it matters. If you need to store a large number of items and never perform membership tests, a list is more memory-efficient. Conversely, if membership testing is frequent, the speed gain from a set often outweighs the extra memory.

There is no universal threshold; the decision depends on your data size and access pattern. Profile your application if memory is a concern.

Converting Between List and Set

You can convert a list to a set to remove duplicates, and a set to a list to make it indexable or to sort it. Both conversions have side effects you should understand.

duplicates = [3, 1, 4, 1, 5, 9, 2, 6, 5] unique = set(duplicates) # {1, 2, 3, 4, 5, 6, 9} unique_list = list(unique) # order not guaranteed sorted_unique = sorted(unique) # [1, 2, 3, 4, 5, 6, 9]

Converting a set to a list loses the unordered nature, but you can sort it explicitly. Converting a list to a set loses duplicates and order. If you need to preserve order while removing duplicates, use the unique_preserve_order pattern shown earlier.

Common Pitfalls with Sets

Sets reject unhashable elements. If you try to create a set of lists, you get a TypeError. This is a common stumbling block when you have a list of lists and want to deduplicate it. In that case, you can convert inner lists to tuples first.

list_of_lists = [[1, 2], [3, 4], [1, 2]] # set(list_of_lists) # TypeError: unhashable type: 'list' unique_tuples = set(tuple(x) for x in list_of_lists) # {(1, 2), (3, 4)}

Another pitfall is assuming iteration order. Sets are unordered, so the order of elements when you iterate or convert to a list is not guaranteed and can change between runs due to hash randomization. Never rely on set order for output that must be deterministic.

Choosing Based on Your Operation Mix

The decision between list and set is rarely binary. Many programs use both: a list for ordered storage and a set for fast lookups of the same data. For example, you might maintain a list of all items for display and a set of item IDs for quick validation.

OperationListSet
Access by indexO(1)Not supported
Append / addO(1) amortizedO(1) average
Membership testO(n)O(1) average
Remove by valueO(n)O(1) average
Preserve orderYesNo
Allow duplicatesYesNo

Use a list when you need positional access, slicing, or ordered iteration. Use a set when uniqueness and fast membership are more important than order. When you need both, maintain both structures and update them together.

A practical pattern is to wrap the synchronization in a small class so the invariant stays consistent:

class OrderedUnique: def __init__(self): self._list = [] self._set = set() def add(self, item): if item not in self._set: self._set.add(item) self._list.append(item) def __iter__(self): return iter(self._list) def __contains__(self, item): return item in self._set

This gives you the best of both worlds, at the cost of maintaining two data structures. The overhead is acceptable when the collection is large and both ordered iteration and fast membership are frequent operations.

Handling Unhashable Data in Sets

When your data is mutable and cannot be hashed directly, you have a few options. Convert the elements to an immutable form, such as tuples, before adding them to a set. If you need to keep the original mutable objects, you cannot use a set directly; you might fall back to a list with a linear membership check, or use a dictionary keyed by a hashable proxy.

# Using a tuple proxy records = [{'id': 1, 'name': 'a'}, {'id': 2, 'name': 'b'}] seen = set() for record in records: key = (record['id'], record['name']) if key not in seen: seen.add(key) # process record

This approach works when you can derive a hashable representation. If the object itself must be stored, consider using a dictionary with id(obj) as the key, but be aware that id() can be reused after the object is garbage collected, so it is not safe for long-lived references.

The choice between list and set ultimately depends on your access patterns. Measure with realistic data if performance is critical, but the general rule is simple: if you need to check membership often, use a set; if you need order or duplicates, use a list. Many production codebases use both, and knowing when to switch is a skill that prevents subtle performance bugs.

python list vs set: Practical Usage and Code Examples | RYUSLOG DEV