Python Introduction to Sets
Table of Contents + −
In the last lesson you learned Tuple vs List. You saw how both keep items in order. You also saw how both let the same value show up many times. But sometimes you do not want repeats. You just want to know which values are there, once each. That is exactly what a set is for.
🤔 Why Do We Need Sets?
Say Alex has a list of email addresses from a signup form. People signed up more than once. So the same email shows up again and again. Now you want the clean list, each person only once.
With a list you would have to do a lot of manual work. Think about the steps:
- You would loop through every email one by one.
- You would keep a separate “seen” pile on the side.
- For each email you would check if it is already in the “seen” pile, then skip it if it is.
That is a lot of code for a simple idea, right? And there is a second pain too. Later you want to ask “did this person already sign up?” On a long list, Python has to walk item by item until it finds a match. So the longer the list gets, the slower that check becomes.
A set fixes both of these. A set is a collection that holds only unique items. Drop the same value in twice and it just keeps one copy. So removing duplicates becomes almost free. And asking “is this value in here?” stays fast even when the set is huge. We will see why a bit later.
🐍 What Is a Set?
A set is an unordered collection of unique items.
Two words there matter. Let us be clear about each one.
- Unique means no duplicates. Every item appears once, no matter how many times you add it.
- Unordered means the items have no fixed position. There is no “first” or “third” item. Python may show them in any order.
Think of a bag of name tags at an event. Each name is on the bag only once. You do not care whether “Riya” is near the top or the bottom of the bag. You only care whether her tag is in there or not. That bag is a set. You reach in and ask “is Riya’s tag here?”, and it is either there or not. No slot numbers, no repeats.
✏️ Creating a Set
You write a set with curly braces {} and commas between the items. This little program makes a set of fruit names and prints it.
fruits = {"apple", "banana", "cherry"}print(fruits)Output
{'cherry', 'apple', 'banana'}So we typed apple, banana, cherry. But the output came back in a different order. That is the unordered part in action. Your own run might print them in yet another order. That is completely fine. A set does not promise any order, so never count on it.
Note
Because a set has no order, you cannot grab an item by position. Writing fruits[0] does not work on a set and will raise an error. Sets are about “is it in here?”, not “what is at slot 0?”.
🪄 Duplicates Just Disappear
Here is the part that makes sets so handy. Put the same value in twice and the set quietly keeps only one. This program adds “apple” three times on purpose.
fruits = {"apple", "apple", "apple", "banana"}print(fruits)print(len(fruits))Output
{'apple', 'banana'}2Let us read it. We wrote four items but the set holds two. The extra “apple” copies were dropped the moment they went in. len() confirms it, since it counts how many items are inside. Only two unique values are left. See, you did not write any loop or any check. The set handled the deduplication for you, which is just a fancy word for “removing duplicates”.
🧹 Turning a List Into a Set to Remove Duplicates
This is the trick you will reach for the most. You hand a list to set() and you get back the same values with the repeats removed. Here we clean up Alex’s signup emails.
emails = [ "alex@mail.com", "riya@mail.com", "alex@mail.com", "arjun@mail.com", "riya@mail.com",]
unique_emails = set(emails)print(unique_emails)print(f"{len(emails)} signups, {len(unique_emails)} real people")Output
{'arjun@mail.com', 'alex@mail.com', 'riya@mail.com'}5 signups, 3 real peopleRead it top to bottom. The list emails has five entries, but two addresses repeat. Then set(emails) walks through the list and builds a set. So each address lands once and the doubles are skipped. We end up with three addresses. The f-string at the end prints both counts side by side, so you can see the cleanup clearly.
Now sometimes you want a plain list back at the end, not a set. Just wrap it again like list(set(emails)). So you go list to set to remove the repeats, then back to list. Remember the order may shift, because a set does not keep order. If you need the original order kept, that is a separate trick we will not need here.
➕ Adding and Removing Items
A set is not frozen after you create it. You can put new items in and take items out whenever you want. Let us walk through the main moves one at a time.
Adding one item with add(). This program starts with a small set of tags and adds one more.
tags = {"python", "coding"}tags.add("beginner")print(tags)Output
{'python', 'coding', 'beginner'}So add() drops a single new item into the set. And if the item is already there, nothing happens. No error, no duplicate. The set just stays as it was.
Adding many items at once with update(). When you want to pour in a whole bunch of items, add() one by one is slow to write. So update() takes a list (or another set) and adds all of them.
tags = {"python"}tags.update(["coding", "beginner", "python"])print(tags)Output
{'python', 'coding', 'beginner'}We passed three items, but “python” was already inside. So only the two new ones got added. The repeat was skipped, just like always.
Removing with remove() versus discard(). Both take an item out of the set. The difference is what happens when the item is not there. Look closely.
tags = {"python", "coding", "beginner"}
tags.remove("coding") # this item exists, so it goes awayprint(tags)
tags.discard("java") # "java" is not here, but discard stays calmprint(tags)Output
{'python', 'beginner'}{'python', 'beginner'}So here is the key difference, in points:
remove()takes the item out, but if that item is not in the set, it raises aKeyErrorand your program crashes.discard()takes the item out too, but if the item is missing, it just does nothing quietly. No error.
Here is what remove() does on a missing item, so you recognize it later.
tags = {"python", "beginner"}tags.remove("java")Output
Traceback (most recent call last): File "main.py", line 2, in <module> tags.remove("java")KeyError: 'java'So the rule is easy. Use discard() when you are not sure the item is there and you do not want a crash. Use remove() when you expect it to be there and you actually want to know if it is missing.
Taking out a random item with pop(). Since a set has no order, pop() removes and gives back some item, but you do not control which one. This program pops an item from a set of colors.
colors = {"red", "green", "blue"}picked = colors.pop()print(picked)print(colors)Output
red{'green', 'blue'}It pulled out “red” this time, but on your machine it might pull a different one. That is the unordered nature again. So only use pop() when you truly do not care which item leaves.
Emptying the whole set with clear(). This one wipes everything and leaves an empty set.
colors = {"red", "green", "blue"}colors.clear()print(colors)Output
set()Notice the empty set prints as set(), not as {}. Hold that thought, because the next section is all about why.
⚠️ The Empty Set Trap
There is one catch that surprises almost everyone. You might expect empty curly braces to make an empty set. They do not. Empty {} makes an empty dictionary instead, which is a different data structure that stores key-value pairs.
This short program shows what each one really is.
a = {}b = set()
print(type(a))print(type(b))Output
<class 'dict'><class 'set'>So type() tells you the real kind of each value. The first one came out as dict, even though we wrote curly braces. The second one is a real set. The reason is history. Python used {} for dictionaries long before sets existed, so those braces were already taken.
So the rule is simple. For an empty set, always call set(). Save {} for dictionaries.
# ❌ Avoid: this is an empty dictionary, not a setscores = {}
# ✅ Good: this is a real empty setscores = set()🧱 What Can Go In a Set?
A set is picky about what it will hold. You can put numbers, strings, and tuples in a set. But you cannot put a list or a dictionary inside one. This program tries to add a list and Python refuses.
ok = {1, "hello", (2, 3)} # numbers, strings, tuples are fineprint(ok)
bad = {[1, 2], [3, 4]} # lists are not allowedprint(bad)Output
{1, (2, 3), 'hello'}Traceback (most recent call last): File "main.py", line 5, in <module> bad = {[1, 2], [3, 4]}TypeError: unhashable type: 'list'The first line worked. The second line crashed with unhashable type: 'list'. So what does hashable mean? In plain words, an item is hashable if Python can turn it into a fixed number tag, called a hash, that never changes. The set uses that tag to find items fast and to spot duplicates.
Here is the catch:
- A list can change after you make it. You can add to it or remove from it. So its tag would keep changing, and the set could not trust it.
- A tuple cannot change once made. So its tag stays fixed, and a set is happy to hold it.
So the simple rule is: only items that do not change are allowed. Numbers, strings, and tuples qualify. Lists and dictionaries do not. If you really need to store a pair like (1, 2) in a set, use a tuple, not a list.
🔍 Checking If Something Is Already There
The other big win is the membership check. You ask “is this value in the set?” with the in keyword. This program checks who is already on the guest list.
guests = {"Alex", "Riya", "Arjun"}
print("Riya" in guests)print("Sam" in guests)Output
TrueFalseThe in keyword gives back True or False. “Riya” is in the set, so we get True. “Sam” is not, so we get False. Same idea as the name-tag bag. You only want to know if the tag is there.
Now here is the part people really love. A set answers in very fast, even when it holds millions of items. Why so fast?
- A list has to walk item by item until it finds a match or reaches the end. So a bigger list means a slower check.
- A set uses that hash tag we talked about. It jumps almost straight to the right spot. So the size barely matters.
So when your main question is “have I seen this before?” and the collection is large, a set is the tool you reach for. That is the whole reason people switch a list to a set in the first place.
🧊 A Quick Word on frozenset
One more term you should have heard at least once: frozenset. A normal set can change, so you can add and remove items. A frozenset is the locked version. Once you make it, you cannot change it at all.
This program makes a frozenset and shows that adding to it fails.
permissions = frozenset({"read", "write"})print(permissions)
permissions.add("delete")Output
frozenset({'read', 'write'})Traceback (most recent call last): File "main.py", line 4, in <module> permissions.add("delete")AttributeError: 'frozenset' object has no attribute 'add'So a frozenset has no add() at all, because it is meant to stay fixed. You will not need it every day. But two times it really helps:
- When you want a set of fixed values that no one should change by accident, like a list of allowed permissions.
- When you want to use a set as a key in a dictionary or as an item inside another set, because a frozenset is hashable and a normal set is not.
For now, just remember the word. A frozenset is a set you cannot change.
🌍 Where Sets Are Used in Real Life
Sets show up everywhere once you start noticing. A few everyday examples:
- Unique tags. A blog post on a site like Medium has tags like “python”, “coding”, “beginner”. You store them in a set so the same tag cannot be added twice.
- Unique visitor IDs. YouTube wants to count how many different people watched a video, not how many clicks. So every viewer’s ID goes into a set, and the size of the set is the real audience.
- Removing duplicate emails. Just like Alex’s signup form, a newsletter on Amazon or Netflix drops every address into a set so no one gets the same email twice.
- Fast “have I seen this?” checks. A web crawler keeps a set of pages it already visited, so it never downloads the same page again.
The pattern is always the same. You care about which values exist, not their order and not how many times they repeat.
⚠️ Common Mistakes
A few slips trip people up early on. Watch for these.
- Using
{}for an empty set. As we just saw, that makes a dictionary. Useset(). - Expecting order. Printing a set will not match the order you typed, and it can change between runs. Never rely on set order.
- Indexing a set.
my_set[0]raises an error because items have no position. If you need positions, use a list. - Using
remove()on something that might be missing. That raises aKeyErrorand crashes. Usediscard()when you are not sure the item is there. - Putting a list inside a set. A set can only hold items that do not change, like numbers, strings, and tuples.
{[1, 2]}raises aTypeError. Use a tuple instead, like{(1, 2)}.
✅ Best Practices
- Reach for a set when you care about which values exist, not their order or how many times they repeat.
- Use
set(my_list)as your go-to way to strip duplicates. - Use
value in my_setfor fast membership checks, especially on large collections. - Prefer
discard()overremove()when the item might not be there and you do not want a crash. - Always create an empty set with
set(), never with{}.
🧩 What You’ve Learned
- ✅ A set is an unordered collection where every item is unique.
- ✅ You build a set with curly braces, like
{"a", "b", "c"}. - ✅ Duplicates are dropped automatically, so adding the same value twice keeps one copy.
- ✅ Sets have no order, so you cannot index them by position.
- ✅
set(my_list)removes duplicates from a list in one step. - ✅ Use
add()andupdate()to put items in, andremove(),discard(),pop(), orclear()to take them out. - ✅
discard()is safe on a missing item, butremove()raises aKeyError. - ✅ Only hashable items go in a set: numbers, strings, and tuples are fine, but lists and dictionaries are not.
- ✅ The
inkeyword checks membership fast, which is the main reason people choose sets. - ✅ An empty set needs
set(), because{}makes an empty dictionary. - ✅ A
frozensetis a set you cannot change after you create it.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What happens when you add a value to a set that is already in it?
Why: Sets hold only unique items, so adding a value that already exists simply keeps the one copy.
- 2
How do you create an empty set in Python?
Why: Empty {} makes a dictionary, so you must use set() to get an empty set.
- 3
What is the difference between remove() and discard() on a set?
Why: Both take an item out, but remove() crashes with a KeyError on a missing item while discard() stays quiet.
- 4
Why can't you put a list inside a set?
Why: A set needs items that do not change (hashable items). A list can change, so Python raises a TypeError. Use a tuple instead.
🚀 What’s Next?
Now that you can build sets, add and remove items, and trust them to keep things unique, the next step is combining and comparing them. You will find what two sets share. You will also find what makes them different.