Sets, Membership & Dedup
The last piece is the set. A set is just a hash map that stores keys and no values - so it inherits the
same O(1) average membership test, minus the bookkeeping of associated data. Whenever the only question is
"have I seen this before?", a set is the right tool.
Membership: "is this in the collection?"
Testing membership against a set is constant time, the same buckets-and-hash idea from Phase 1. Against a list it would be a linear scan.
= # a set literal
True
False
What just happened: each in test hashed the string and checked one bucket - no walking the collection.
For a handful of names it hardly matters, but for a blocklist of a million entries checked on every request,
set-vs-list is the difference between instant and sluggish.
De-duplication that preserves order
Need the unique items, in the order they first appeared? Walk once, keeping a set of what you've already
emitted. The set answers "seen it?" in O(1); the result list keeps the original order.
=
=
return
[3, 1, 2, 5]
What just happened: the first time each value shows up it's added to both seen and result; every later
repeat is caught by x not in seen and skipped. One pass, order preserved. (If you don't care about
order, list(set(items)) is the one-liner - but it may reorder the elements, for reasons we hit at the
bottom of this phase.)
Finding the first duplicate
Same set, slightly different question: what's the first value that repeats? Return it the moment you see a value already in the set.
=
return
return None
4
What just happened: 2, 4, 3 all go into seen as first sightings. The next 4 is already there, so
it's returned immediately - the scan stops early instead of running to the end. 2 also repeats later, but
4's repeat comes first.
Gotcha 1: keys (and set members) must be hashable
A hash map can only hash things that don't change - so mutable values like lists and dicts can't be keys or set members. Immutable ones (numbers, strings, tuples) are fine.
=
# a tuple is immutable -> hashable, fine
# a list is mutable -> unhashable
True
Cannot add a list: unhashable type
What just happened: the tuple (1, 2) hashes fine and goes in. Trying to add the list [1, 2] raises a
TypeError for an unhashable type - because a list can change after insertion, its hash would go stale and
the map could never find it again. The fix when you need a list-like key is to convert it to a tuple first.
Gotcha 2: the worst case really is O(n)
"O(1) average" is a promise about the typical case. If many keys collide into the same bucket, the map
degrades toward a linear scan of that bucket. In normal use this never bites - hash functions spread keys
well - but it's why the guarantee is "average," not "always." For adversarial input (a service where users
control keys), it's a real, if rare, concern.
Gotcha 3: don't rely on set ordering
A Python dict preserves insertion order (guaranteed since Python 3.7). A set does not - its
iteration order follows the internal bucket layout, which you shouldn't depend on.
= # a set, not sorted, not insertion-ordered
# ask explicitly if you want an order
{50, 20, 40, 10, 30}
[10, 20, 30, 40, 50]
What just happened: the set prints in some bucket-driven order that is neither sorted nor the order written
- and that exact ordering is an implementation detail you should never build logic on. When order matters,
be explicit: use
sorted(...)for sorted output, or the order-preservingdedupefrom earlier for first-seen order. (Your printed line for the set above may differ from what's shown here, which is exactly the point.)
💡 Key point. Reach for a set when you only need "seen it or not," a dict when you need to associate a value (a count, an index, an object) with each key, and a list when order and duplicates both matter. Picking the right one is most of what makes hash-based code fast and correct.
[
{
"q": "Why can't a Python list be used as a set member or dict key?",
"choices": ["Lists are too big", "Lists are mutable, so their hash could change and the map could no longer find them - they're unhashable", "Lists are slower than tuples", "It's only a style rule, not enforced"],
"answer": 1,
"explain": "Hash-based structures need a stable hash. A mutable list could change after insertion, invalidating its hash, so Python forbids it with `TypeError: unhashable type`."
},
{
"q": "You need the unique items from a list, in the order they first appeared. What's the safest approach?",
"choices": ["list(set(items)) - it always keeps order", "Walk once with a `seen` set, appending each first-seen item to a result list", "Sort the list, then remove neighbors", "There's no way to preserve order"],
"answer": 1,
"explain": "`list(set(...))` may reorder because set iteration order isn't guaranteed. A `seen` set plus a result list keeps O(1) membership checks while preserving first-seen order."
}
]
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 2 questions
1. Why can't a Python list be used as a set member or dict key?
2. You need the unique items from a list, in the order they first appeared. What's the safest approach?