Back to Blog
Python

Python Set vs List: When to Use Each

python set vs list: Compare Python sets and lists: ordering, duplicates, membership testing, memory, and when each structure is the right choice.

PythonData StructuresSetsListsPerformance
Illustration comparing an ordered list of blocks with a set of unique blocks and instant membership lookup.

Choosing between a Python set and a list comes down to what the data represents and how it will be queried. A list preserves insertion order, allows duplicates, and supports positional access. A set guarantees that each element is unique, provides near-constant-time membership checking, and does not preserve insertion order. That difference drives most practical decisions around python set vs list.

What a List Guarantees

A list is an ordered, mutable sequence. Elements are stored in the order they were appended, and each element can be accessed by index. Duplicates are allowed, so the same value can appear multiple times.

tasks = ["parse", "validate", "parse"] print(tasks[0]) # parse print(tasks.count("parse")) # 2

The order guarantee matters when position carries meaning, such as a processing pipeline where steps must run in a specific sequence. Lists also support slicing, negative indexing, and in-place modification through methods like append, insert, and pop.

What a Set Guarantees

A set is an unordered collection of unique, hashable elements. Adding a value that already exists has no effect, and there is no index-based access because the elements are stored by hash rather than by position.

tags = {"python", "data", "python"} print(tags) # {'python', 'data'} in some order

The set API is designed around the mathematical set operations: union, intersection, difference, and symmetric difference. If the task is deduplication or membership testing, a set expresses that intent directly.

Membership Testing Cost

The most significant runtime difference between the two structures is how membership is checked.

A list has no index over its contents. Testing whether a value is present requires a linear scan from the first element until a match is found or the list ends. That is O(n) in the worst case.

A set stores elements in a hash table. Computing the hash of the value and probing the table gives an average-case O(1) lookup. The cost of hashing the element itself still applies, but it does not grow with the number of stored elements.

if "error" in log_messages: # list: linear scan handle_error() if "error" in error_set: # set: hash lookup handle_error()

For a small collection the difference is negligible. For a large collection checked repeatedly, the list scan becomes the dominant cost. The mechanism matters more than any single benchmark: repeated in checks against a list of thousands of elements multiply the linear scan cost, while a set keeps each check near constant.

What Can Be Stored in Each

A list can hold any Python object, including other lists, dictionaries, and other mutable types. There is no requirement that elements be comparable or hashable.

A set requires every element to be hashable, because hashing is how the set stores and locates elements. Mutable built-in types such as list, dict, and set are not hashable and cannot be added to a set.

valid = ["a", "b"] # valid_set = {["a", "b"]} # TypeError: unhashable type: 'list'

If the data consists of mutable objects that must be deduplicated, a set is not a direct option. A tuple, being immutable and hashable, can be stored in a set when the contents are themselves hashable.

Operations That Differ

The two types expose different operation sets because they serve different purposes.

Adding an element to a list uses append, which places the value at the end and preserves order. Adding to a set uses add, which inserts the value if it is not already present.

items = [] items.append("x") unique = set() unique.add("x") unique.add("x") # no effect

Removal also differs. A list can remove by value with remove, which scans for the first match, or by index with pop. A set removes by value with discard or remove; discard is safe when the element may be absent, while remove raises KeyError.

Set-specific operations such as union and intersection have no list equivalent. Combining two lists with + concatenates them and preserves duplicates, which is not the same as merging unique values.

a = {1, 2, 3} b = {3, 4} print(a | b) # {1, 2, 3, 4} print(a & b) # {3}

Memory and Runtime Tradeoffs

A list is a compact dynamic array. It stores element references contiguously, with some spare capacity for growth. The memory overhead per element is low.

A set is backed by a hash table, which requires a larger underlying array to keep the load factor low and collisions manageable. The same number of elements therefore occupies more memory in a set than in a list. The tradeoff is acceptable when membership testing or deduplication is the primary operation, because the memory cost buys the constant-time lookup.

There is also a construction cost. Building a set from an iterable hashes every element once. Building a list simply appends references. For one-time processing where the collection is never queried for membership, a list is cheaper to build and iterate.

Choosing Between Set and List

The decision depends on what the code does with the collection after it is built.

CriterionUse listUse set
Order mattersYesNo
Duplicates allowedYesNo
Index access neededYesNo
Frequent membership testsNoYes
Elements are mutableYesNo
Deduplication neededNoYes

When position, ordering, or duplicate values carry meaning, a list is the correct structure. When the collection exists to answer "is this value present" or to remove duplicates, a set is the correct structure.

A common pattern is to build a list for ordered processing and convert it to a set for a one-time deduplication step:

records = ["a", "b", "a", "c"] unique_records = list(set(records))

Note that this conversion loses the original order. If order must be preserved while removing duplicates, a dict-based approach or an explicit loop that tracks seen values is needed.

Common Mistakes and Edge Cases

Assuming a set preserves insertion order is a frequent error. Sets are unordered by definition, and relying on iteration order produces code that may appear to work in one run and break in another. Python's dict preserves insertion order, but a set does not.

Treating a list as a set for membership testing is another common mistake. When a list is large and checked repeatedly, converting it to a set once and reusing the set avoids repeated linear scans.

The empty set is written as set(), not {}. The literal {} creates an empty dictionary. This distinction is a common source of subtle bugs when a function returns an empty collection.

Hashability also affects what can be used as a set element. If a value is a list that must be deduplicated, converting it to a tuple first is the standard approach, provided the contents are hashable.

The final consideration is intent. A set communicates uniqueness and set semantics to the next reader of the code. A list communicates order and repetition. Choosing the structure that matches the data's meaning reduces both bugs and the cognitive load of maintaining the code.

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