Performing Set Operations

Sets become powerful once you combine them. The four fundamental set operations — union, intersection, difference, symmetric difference — let you answer questions like "which users are in both lists?", "which items are only in file A?" and "what's the full vocabulary?" in a single expression. Each operation is available both as an operator (|, &, -, ^) and as a method (union, intersection, difference, symmetric_difference).

The operator forms require both sides to be sets; the method forms accept any iterable. That is a small but useful distinction: roles.union(["admin"]) works while roles | ["admin"] raises TypeError. In practice, lean on the operators when both sides are already sets for maximum clarity and on the methods when you want to accept flexible input.

Every binary set operation has an in-place counterpart that modifies the left-hand set instead of returning a new one: |=, &=, -=, ^=, plus update, intersection_update, difference_update, symmetric_difference_update. Use these when the set is large and you don't need to keep the original.

Two other operators come up surprisingly often: a <= b (subset, equivalent to a.issubset(b)) and a >= b (superset). They make set algebra read like English in tests and preconditions: assert required_fields <= received_fields.

The four fundamental operations

a | b is the union: every item that appears in either. a & b is the intersection: items in both. a - b is the difference: items in a but not b. a ^ b is the symmetric difference: items in exactly one of the two.

All four are commutative except difference: a - b is not the same as b - a. If you need "items unique to either side", symmetric difference is the one-stop answer.

Subset, superset and disjoint checks

a.issubset(b) and a <= b both return True when every item of a is in b. Proper subset is written a < b. a.isdisjoint(b) returns True when the two have no items in common — a fast check that avoids building the intersection.

These predicates are the foundation of many validation tasks: "does the request carry every required field?", "does the user's set of roles include admin?", "are these two feature flags mutually exclusive?".

The set-algebra operators and their named counterparts.

ToolPurpose
a | b / a.union(b)
operator/method
Items in either set.
a & b / a.intersection(b)
operator/method
Items in both sets.
a - b / a.difference(b)
operator/method
Items only in a.
a ^ b
operator
Items in exactly one of the two.
a <= b / a.issubset(b)
operator/method
Every a item is in b.
a >= b / a.issuperset(b)
operator/method
Every b item is in a.
a.isdisjoint(b)
method
No items in common.
a |= b
augmented op
Union, in place.

Performing Set Operations code example

The script below models two teams of users and answers typical questions with set algebra.

# Lesson: Performing Set Operations
team_a = {"ana", "ben", "cai", "dev"}
team_b = {"cai", "dev", "eli", "fay"}

everyone = team_a | team_b
shared   = team_a & team_b
only_a   = team_a - team_b
only_one = team_a ^ team_b

print("everyone :", sorted(everyone))
print("shared   :", sorted(shared))
print("only A   :", sorted(only_a))
print("only one :", sorted(only_one))

# Subset / superset / disjoint predicates
required = {"ana", "ben"}
print("A covers required?", required <= team_a)
print("B covers required?", required <= team_b)
print("disjoint teams?   ", team_a.isdisjoint(team_b))

# In-place update
team_a |= {"eli"}
print("A after |= eli:", sorted(team_a))

# Method forms accept any iterable
mixed = team_b.union(["gio", "hal"])
print("union with list:", sorted(mixed))

The script is organized around four ideas:

1) `|`, `&`, `-`, `^` are the core operators; each returns a new set.
2) `<=`, `>=`, `isdisjoint` are predicates; they return booleans.
3) `|=` and friends modify the set in place; useful for large data.
4) `.union()` / `.intersection()` accept any iterable; operators require sets.

Practice checks and differences on small sets.

# Example A: required fields check
required = {"name", "email"}
received = {"name", "email", "age"}
missing = required - received
print("ok" if not missing else f"missing: {missing}")

# Example B: tags changed between runs
before = {"py", "sql"}
after  = {"py", "rust"}
print("added  :", after - before)
print("removed:", before - after)

These assertions are the precise algebra.

assert ({1, 2} | {2, 3}) == {1, 2, 3}
assert ({1, 2} & {2, 3}) == {2}
assert ({1, 2} - {2, 3}) == {1}
assert {1, 2} <= {1, 2, 3}
assert {1, 2}.isdisjoint({3, 4})

The script prints:

everyone : ['ana', 'ben', 'cai', 'dev', 'eli', 'fay']
shared   : ['cai', 'dev']
only A   : ['ana', 'ben']
only one : ['ana', 'ben', 'eli', 'fay']
A covers required? True
B covers required? False
disjoint teams?    False
A after |= eli: ['ana', 'ben', 'cai', 'dev', 'eli']
union with list: ['cai', 'dev', 'eli', 'fay', 'gio', 'hal']