Sets in Python

A set (set) in Python solves two specific tasks: fast membership testing (is an element in the collection?) and storing only unique values without duplicates. It's an unordered collection based on the mathematical concept of a set.

What is a set?

A set in Python is an unordered collection of unique elements. Two key properties of sets:

  1. Unordered: elements have no specific order and are not indexed — you can't reach for "the third element of a set", only iterate over all of them or check whether a particular one is present
  2. Unique: each element appears only once

Main characteristics of sets:

  • Mutability: you can add and remove elements
  • Immutable elements: only immutable objects can go inside a set (numbers, strings, tuples)
  • Efficiency: optimized for fast membership testing

Because sets are based on the mathematical concept, they support union, intersection, and difference operations.

Creating sets

Using curly braces

Python 3.13
# Set of integers
numbers = {1, 2, 3, 4, 5}
print(numbers)
{1, 2, 3, 4, 5}
# Automatic duplicate removal
duplicates = {1, 2, 2, 3, 3, 3, 4, 5, 5}
print(duplicates)
{1, 2, 3, 4, 5}
# A single set can hold different immutable types
mixed = {1, "hello", (1, 2, 3)}
print(len(mixed))  # number, string and tuple — all three fit
3

Using the set() constructor

Python 3.13
# Empty set
empty_set = set()
print(empty_set)
set()
# Creating a set from a list
numbers_set = set([1, 2, 2, 3, 4, 4, 5])
print(numbers_set)
{1, 2, 3, 4, 5}
# Creating a set from a string — repeated letters collapse
letters = set("hello")
print(len(letters))  # "hello" has two 'l', the set keeps one — 4 letters total
4

Basic operations with sets

Checking for element presence

Python 3.13
fruits = {"apple", "banana", "cherry"}

print("apple" in fruits)
True
print("pear" in fruits)
False

Adding and removing elements

The order of elements in a set is arbitrary, so in the string examples below we print them via sorted(), which returns a sorted list — that keeps the output from jumping around between runs.

Python 3.13
fruits = {"apple", "banana"}

# Adding a single element
fruits.add("cherry")
print(sorted(fruits))
['apple', 'banana', 'cherry']
# Adding multiple elements
fruits.update(["pear", "orange"])
print(sorted(fruits))
['apple', 'banana', 'cherry', 'orange', 'pear']
# Removing an element
fruits.remove("banana")  # raises KeyError if element doesn't exist
print(sorted(fruits))
['apple', 'cherry', 'orange', 'pear']
# Safely removing an element
fruits.discard("cherry")  # doesn't raise an error if element doesn't exist
print(sorted(fruits))
['apple', 'orange', 'pear']
# pop() removes and returns some element — which one exactly is not known in advance
removed = fruits.pop()
print(len(fruits))  # one fewer than before
2
# Clearing the set
fruits.clear()
print(fruits)
set()

Looping over a set

You go through a set with a for loop. The order is arbitrary and may change from run to run — that's what "unordered" means. When you need a predictable order, sort with sorted():

Python 3.13
colors = {"red", "blue", "green"}

for color in sorted(colors):
    print(color)
blue
green
red

Mathematical set operations

Three main operations: union, intersection, and difference. They're easy to visualise with Venn diagrams:

Venn diagrams for the three set operations: union A | B, intersection A & B, and difference A - B

Union

All elements from both sets:

Python 3.13
a = {1, 2, 3}
b = {3, 4, 5}

union_set = a | b
print(union_set)
{1, 2, 3, 4, 5}

The same can be written as a.union(b).

Intersection

Elements that are in both sets:

Python 3.13
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

intersection_set = a & b
print(intersection_set)
{3, 4}

The same can be written as a.intersection(b).

Difference

Elements from the first set that are not in the second:

Python 3.13
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

difference_set = a - b
print(difference_set)
{1, 2}

The same can be written as a.difference(b).

Comparing sets

Python 3.13
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {1, 2, 3}

# Set equality
print(a == c)  # Contains the same elements
True
# Subsets
print(a.issubset(b))  # All elements of a are in b
True
print(a < b)  # a is a proper subset of b
True
# Supersets
print(b.issuperset(a))  # b contains all elements of a
True
print(b > a)  # b is a proper superset of a
True
# Checking for no common elements
d = {6, 7, 8}
print(a.isdisjoint(d))  # No common elements
True

Immutable sets (frozenset)

If you need an immutable version of a set, use frozenset:

Python 3.13
# Creating a frozenset
immutable_set = frozenset([1, 2, 3, 4])
print(immutable_set)
frozenset({1, 2, 3, 4})
# Attempting to modify a frozenset raises an error
try:
    immutable_set.add(5)
except AttributeError as e:
    print(f"Error: {e}")
Error: 'frozenset' object has no attribute 'add'
# frozenset can be used as a dictionary key or an element of another set
normal_set = {frozenset([1, 2]), frozenset([3, 4])}
print(len(normal_set))  # both frozensets fit inside
2

Practical examples of using sets

1. Removing duplicates from a list

Python 3.13
numbers = [1, 2, 2, 3, 3, 3, 4, 5, 5]
unique_numbers = list(set(numbers))
print(unique_numbers)
[1, 2, 3, 4, 5]

2. Finding common elements

Python 3.13
users_group1 = ["Anna", "Ivan", "Maria", "Peter", "Elena"]
users_group2 = ["Ivan", "Olga", "Elena", "Alex"]

# Common elements (intersection)
common_users = set(users_group1) & set(users_group2)
print(f"Users in both groups: {sorted(common_users)}")
Users in both groups: ['Elena', 'Ivan']
# Elements only in the first group (difference)
only_group1 = set(users_group1) - set(users_group2)
print(f"Only in group 1: {sorted(only_group1)}")
Only in group 1: ['Anna', 'Maria', 'Peter']
# All unique elements (union)
all_users = set(users_group1) | set(users_group2)
print(f"All unique users: {sorted(all_users)}")
All unique users: ['Alex', 'Anna', 'Elena', 'Ivan', 'Maria', 'Olga', 'Peter']

3. Checking for uniqueness of elements

Python 3.13
def are_all_unique(items):
    """Checks if all elements in a sequence are unique."""
    return len(set(items)) == len(items)

print(are_all_unique([1, 2, 3, 4, 5]))
True
print(are_all_unique([1, 2, 3, 3, 4]))
False

Limitations and performance

Limitations

Set elements must be hashable (immutable):

Python 3.13
# Works with immutable data types
valid_set = {1, "hello", (1, 2, 3)}
print(len(valid_set))  # number, string and tuple are all hashable — all three fit
3
# Error with mutable data types
try:
    invalid_set = {1, [2, 3], {"a": 1}}
except TypeError as e:
    print(f"Error: {e}")
Error: unhashable type: 'list'

You can add:

  • Numbers (int, float, complex)
  • Strings (str)
  • Tuples (tuple) with hashable elements
  • Frozenset

You cannot add:

  • Lists (list)
  • Dictionaries (dict)
  • Sets (set)

Performance

Fast lookup is exactly what sets are built for. Let's test on a million numbers: we look for the last one — the worst case for a list, which has to scan through everything.

Python 3.13
import time

data = list(range(1_000_000))
data_set = set(data)

start = time.time()
for _ in range(100):
    999_999 in data
list_time = time.time() - start

start = time.time()
for _ in range(100):
    999_999 in data_set
set_time = time.time() - start

print(f"Search in list: {list_time:.3f} sec")
Search in list: 0.442 sec
print(f"Search in set: {set_time:.5f} sec")
Search in set: 0.00001 sec

Your exact numbers will differ — they depend on the machine and how busy it is — but the gap stays just as wide: tens of thousands of times. The list has to check elements one by one until it finds the right one. Instead of scanning, a set computes straight away where the value should sit and checks only that spot — and it does so equally fast whether there are ten elements or a million.

Operations with O(1) complexity (constant time):

  • Testing for membership: x in set
  • Adding an element: set.add(x)
  • Removing an element: set.remove(x), set.discard(x)

Check your understanding

What does print(set([1, 2, 2, 3, 3, 3])) output?