AlgoInsight

Free lesson · Python Basic to Advance

Set Basics

Easy6 min readpython

Set Basics

What a Set Is

A set is an unordered collection of unique, hashable values. Sets are the Python equivalent of mathematical sets - and they expose the same operations: union, intersection, difference, and symmetric difference.

Two distinguishing properties shape every use of sets:

The combination makes sets ideal for membership testing, deduplication, and set-arithmetic problems. Lookup, insertion, and deletion all run in O(1) average time.

Creating a Set

The most common form is a literal - comma-separated values inside curly braces:

primes = {2, 3, 5, 7, 11}
print(primes)
# {2, 3, 5, 7, 11}
This code creates a set of five prime numbers using a set literal. The curly braces with comma-separated values produce a set object.

The set() constructor builds a set from any iterable:

from_list = set([1, 2, 2, 3, 3, 3])
print(from_list)
# {1, 2, 3}

from_string = set("hello")
print(from_string)
# {'h', 'e', 'l', 'o'}    (order varies)
This code builds sets from a list with duplicates and from a string. Duplicates are silently dropped - the resulting set has each value only once.

This is the canonical pattern for deduplication: pass any iterable to set() and the duplicates disappear.

The empty set gotcha

{} is an empty dictionary, not an empty set. The empty set must be created with set():

empty_dict = {}
print(type(empty_dict))
# <class 'dict'>

empty_set = set()
print(type(empty_set))
# <class 'set'>
This code shows the empty-set gotcha. Empty curly braces create an empty dictionary, not an empty set. To create an empty set, use the set constructor with no arguments.

This is one of Python's most consistent surprises. The curly-brace literal is biased toward the older dict syntax, so the empty form belongs to dicts.

Interactive animation: Set literals and set() from an iterable, then the gotcha - {} makes a dict, so an empty set needs set().

Uniqueness

Adding an existing element to a set is a no-op:

seen = {1, 2, 3}
seen.add(2)
print(seen)
# {1, 2, 3}
This code adds the value two to a set that already contains it. The set is unchanged. Adding an existing element does not raise an error and does not duplicate the value.

This makes sets ideal for tracking "has this been seen before?" - the common pattern in graph traversal, deduplication, and uniqueness-counting problems.

Interactive animation: Adding an existing element changes nothing, and because True == 1 the two count as one member.

Hashability Requirement

Set elements must be hashable. Built-in immutable types (int, float, str, bytes, tuple of hashables, frozenset) are hashable. Mutable types (list, dict, set) are not:

{1, 2, 3}        # ok - ints are hashable
{"a", "b"}       # ok - strings are hashable
{(1, 2), (3, 4)} # ok - tuples of hashables are hashable

{[1, 2]}
# TypeError: unhashable type: 'list'
This code shows what can and cannot be a set element. Hashable types are accepted. A list cannot be a set element because lists are mutable, which would make their hash value unreliable.

This rule matches the dict-key rule from Topic 6.1 - sets and dicts share the same underlying hash table machinery.

Order is Not Defined

A set has no defined iteration order. Modern CPython produces a consistent order for a given set of hashes, but that order is implementation detail and should not be relied on:

s = {3, 1, 4, 1, 5, 9, 2, 6}
print(s)
# {1, 2, 3, 4, 5, 6, 9}    (order may vary by run/version)
This code shows that printing a set may produce elements in an order that looks sorted but is actually implementation-dependent. Code that depends on set iteration order is incorrect - convert to a list and sort if order matters.

When stable order is required, convert to a list and sort: sorted(my_set).

Core Operations

A set supports the standard collection operations:

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

print(len(fruits))
# 3

print("apple" in fruits)
# True

print("grape" in fruits)
# False

for fruit in fruits:
    print(fruit)
# apple
# banana
# cherry          (order may vary)
This code demonstrates length, membership testing, and iteration on a set. Membership testing is the most common operation and runs in constant time on average.

Membership testing (x in s) is the single biggest reason to choose a set over a list. A list's in is O(n); a set's in is O(1) average.

# Slow on large data - O(n) per check
items = [1, 2, 3, ..., 10_000_000]
if target in items:    # linear scan
    ...

# Fast on large data - O(1) average per check
items = {1, 2, 3, ..., 10_000_000}
if target in items:    # hash lookup
    ...
This code contrasts the performance of in on a list and a set. On a list, in performs a linear scan. On a set, in performs a hash lookup that is constant time on average. For repeated membership checks, the set is dramatically faster.

Interactive animation: A membership check inside a loop - 100M comparisons against a list versus 10k hashes against a set.

Common Uses

Three patterns drive most set usage:

Deduplication. Convert a sequence to a set and back to a list to drop duplicates: list(set(my_list)). Note that this loses the original order - use a dict.fromkeys() trick (Topic 8.2) when order matters.

Fast membership testing. Build a set once, query it many times:

banned_words = {"foo", "bar", "baz"}

def clean(text):
    return [word for word in text.split() if word not in banned_words]

print(clean("foo hello bar world"))
# ['hello', 'world']
This code uses a set of banned words for fast membership filtering. Building the set once, then querying it many times, is far faster than scanning a list of banned words for each input word.

Set arithmetic. Compute relationships between collections - what's common, what's different, what's in either but not both. Covered in detail in Topic 7.2.

Interview Relevance

Interview: Sets appear constantly in interview problems:

  • The visited set in graph and grid traversal - the single most common set use in interviews
  • Deduplication when problem constraints don't allow duplicates in output
  • Fast membership testing when a problem checks "is this element in collection X?" many times
  • Set arithmetic when a problem asks for common elements, missing elements, or symmetric differences

The most-tested set fact: O(1) average membership testing. If a brute-force solution does for x in list_a: if x in list_b, the optimization is usually to convert list_b to a set, reducing total time from O(n × m) to O(n + m).

A frequent question: why is {1, 2, 3} a set but {} a dict? The answer is historical - dict literals predate set literals in Python.

Module 30 covers Python interview preparation.

Common Pitfalls

Warning: The empty-set gotcha. {} is an empty dict. Use set() for an empty set.

Trying to put unhashable values in. A list, dict, or set cannot be an element of a set. Convert lists to tuples first if grouping is needed.

Relying on set iteration order. Sets are unordered. The order observed in CPython is implementation detail and may change. Use sorted(s) when order matters.

Forgetting that set() of a string splits to characters. set("abc") produces {'a', 'b', 'c'}, not {"abc"}. To create a single-element set with a string, use {"abc"} (literal form).

Mutable element inside a tuple inside a set. A tuple is hashable only if all its elements are. (1, [2]) cannot be a set element because the inner list is unhashable.

Set deduplication loses order. list(set(my_list)) drops duplicates but also reorders. Use list(dict.fromkeys(my_list)) to dedupe while preserving first-occurrence order.

Try It

Exercise: Build set fluency:

  1. Create a set of the integers 1 through 10 using a literal, then a second set of the same values using set(range(...)). Verify they are equal.
  2. Write code that demonstrates the empty-set gotcha: create x = {}, check its type, then create the correct empty set.
  3. Build a set containing two tuples. Verify that attempting to add a list to the set raises TypeError.
  4. Given a list with duplicates, dedupe it two ways: with set() (order lost) and with dict.fromkeys() (order preserved). Compare the results.
  5. Time the difference between target in [...] (a list of 10,000 elements) and target in {...} (a set of the same 10,000 elements) using timeit.
  6. Iterate over a set of strings and print them. Run twice - note that the order may differ between runs in some Python versions.

Back to Python Basic to Advance