Last modified: Mar 17, 2026 By Alexander Williams
Python Set of Sets: Nested Collections Guide
Python sets are powerful. They store unique, unordered items. But what if you need a set that contains other sets? This is a "set of sets". It is a more advanced data structure.
This concept is useful for complex problems. Think of grouping unique tags, managing network nodes, or finding distinct combinations. A set of sets can help.
However, Python has a key restriction. A standard set is mutable. You cannot put a mutable object inside another set. This article will explain why and show you how to work around it.
Why Can't You Have a Set of Sets Directly?
In Python, only immutable (unchangeable) objects can be members of a set. This is because sets use a hash table internally. An object's hash value must not change while it is in the set.
A standard Python set is mutable. You can add and remove items from it. Therefore, its hash value could change. Python prevents this by raising a TypeError.
Try to create a set of sets directly. You will see the error.
# This will cause a TypeError
try:
inner_set_1 = {1, 2, 3}
inner_set_2 = {4, 5}
set_of_sets = {inner_set_1, inner_set_2} # Error: set is unhashable
except TypeError as e:
print(f"Error: {e}")
Error: unhashable type: 'set'
The error message is clear. A set is an "unhashable type". You cannot use it as an element in another set. For a deeper understanding of set fundamentals, see our Python Sets Guide: Unordered Unique Collections.
The Solution: Use Frozenset
Python provides a built-in solution: frozenset. A frozenset is an immutable version of a set. Once created, you cannot add or remove items. This makes it hashable and perfect for our needs.
You create a frozenset just like a set, but with the frozenset() constructor. It accepts any iterable, like a list or a regular set.
# Create frozensets from regular sets
regular_set_a = {1, 2, 3}
regular_set_b = {3, 4, 5}
frozen_a = frozenset(regular_set_a)
frozen_b = frozenset(regular_set_b)
print(f"Frozenset A: {frozen_a}")
print(f"Frozenset B: {frozen_b}")
print(f"Type of frozen_a: {type(frozen_a)}")
Frozenset A: frozenset({1, 2, 3})
Frozenset B: frozenset({3, 4, 5})
Type of frozen_a: <class 'frozenset'>
Now you can create your set of sets. Use frozensets as the elements.
# Creating a valid set of sets using frozensets
set_of_frozensets = {frozen_a, frozen_b}
print("Set of Frozensets:", set_of_frozensets)
print("Number of unique frozensets:", len(set_of_frozensets))
Set of Frozensets: {frozenset({1, 2, 3}), frozenset({3, 4, 5})}
Number of unique frozensets: 2
Working with a Set of Frozensets
Once you have your structure, you can perform standard set operations. You can add new frozensets, check for membership, and find unions or intersections at the outer level.
To add a new inner set, you must first convert it to a frozenset. Use the add() method on the outer set. For more on adding items, read Python Set Insert: How to Add Items.
# Adding a new frozenset to the collection
new_set = {6, 7}
new_frozen = frozenset(new_set)
set_of_frozensets.add(new_frozen)
print("After adding a new frozenset:", set_of_frozensets)
# Checking membership
check_frozen = frozenset({1, 2, 3})
print(f"Is {check_frozen} in the collection? {check_frozen in set_of_frozensets}")
After adding a new frozenset: {frozenset({1, 2, 3}), frozenset({6, 7}), frozenset({3, 4, 5})}
Is frozenset({1, 2, 3}) in the collection? True
Remember, the uniqueness property applies to the frozensets. If you try to add a duplicate frozenset, it will be ignored.
# Trying to add a duplicate frozenset (same elements as frozen_a)
duplicate = frozenset([3, 2, 1]) # Order doesn't matter in sets
set_of_frozensets.add(duplicate)
print("After trying to add a duplicate:", set_of_frozensets)
After trying to add a duplicate: {frozenset({1, 2, 3}), frozenset({6, 7}), frozenset({3, 4, 5})}
Practical Example: Managing Unique Groups
Let's see a real-world use case. Imagine you are analyzing social networks. You have different friend groups. You want to store all unique group compositions.
A set of frozensets is ideal. Each inner frozenset is a unique group of friend IDs. The outer set ensures you don't store the same group twice.
# Example: Storing unique friend groups
group1 = frozenset({"Alice", "Bob", "Charlie"})
group2 = frozenset({"Bob", "Charlie", "Diana"})
group3 = frozenset({"Alice", "Bob", "Charlie"}) # Duplicate of group1
unique_groups = {group1, group2, group3} # group3 will not be added
print("Unique Friend Groups:")
for idx, group in enumerate(unique_groups, 1):
print(f" Group {idx}: {set(group)}") # Convert back to set for display
# Finding groups that contain a specific person
person = "Bob"
groups_with_bob = {g for g in unique_groups if person in g}
print(f"\nGroups containing '{person}': {[set(g) for g in groups_with_bob]}")
Unique Friend Groups:
Group 1: {'Charlie', 'Bob', 'Alice'}
Group 2: {'Charlie', 'Bob', 'Diana'}
Groups containing 'Bob': [{'Charlie', 'Bob', 'Alice'}, {'Charlie', 'Bob', 'Diana'}]
This structure efficiently manages uniqueness at two levels. The inner frozenset ensures each group's members are unique. The outer set ensures the collection of groups itself has no duplicates. For more basic set examples, check out Python Sets Examples: Code for Unique Data.
Limitations and Considerations
Using frozensets solves the hashability problem. But it introduces a trade-off: immutability. You cannot modify a frozenset after creation.
If you need to "update" an inner set, you must follow these steps:
- Remove the old frozenset from the outer set.
- Create a new frozenset with the desired changes.
- Add the new frozenset back to the outer set.
# Simulating an update to an inner set
outer_set = {frozenset({1, 2}), frozenset({3, 4})}
print("Original:", outer_set)
# We want to add '5' to the frozenset {3, 4}
target = frozenset({3, 4})
if target in outer_set:
outer_set.remove(target) # Step 1: Remove old
new_elements = set(target) # Convert to a regular set to modify
new_elements.add(5) # Step 2: Make change
new_frozen = frozenset(new_elements) # Create new frozenset
outer_set.add(new_frozen) # Step 3: Add back
print("After update:", outer_set)
Original: {frozenset({3, 4}), frozenset({1, 2})}
After update: {frozenset({1, 2}), frozenset({3, 4, 5})}
This process is more verbose than modifying a regular nested structure. You must decide if the uniqueness guarantee is worth the extra steps.
Conclusion
A Python set of sets is a valuable tool. It models problems involving collections of unique groups. The direct approach fails because sets are mutable and unhashable.
The solution is to use frozenset. This immutable variant behaves like a set but can be placed inside another set. It allows you to build powerful, nested unique collections.
Remember the key steps. Convert your inner sets to frozensets. Use these frozensets as elements in your outer set. You can then add, remove, and check for membership as usual.
This pattern is useful in many areas. Use it for data deduplication, graph theory, or combinatorial problems. It adds a layer of sophistication to your Python data structures. Mastering it will help you write cleaner, more efficient code for complex tasks.