How to Remove Duplicates From a Python List and Keep the Order
To remove duplicates from a Python list and keep the original order, track the items you have seen in a set, not a list - or use list(dict.fromkeys(items)). A membership check on a list scans every element, so a dedup loop becomes O(n^2) and gets very slow on large inputs. A set checks membership in constant time on average, so the loop is O(n).
The slow version
This function removes duplicate events and keeps the first occurrence of each:
def dedup(events: list[str]) -> list[str]:
seen = []
out = []
for event in events:
if event not in seen: # scans the whole list every time
seen.append(event)
out.append(event)
return out
It is correct, and with 100 events it is instant. With 100,000 events it can take minutes. The code did not change - only the input size did.
Why it is slow: O(n^2)
event not in seen on a list compares event with each element, one by one,
until it finds a match or reaches the end. As seen grows, every check costs
more. For n events with many unique values, the total work grows with
n * n:
| Events | Comparisons (roughly, all unique) |
|---|---|
| 1,000 | 500,000 |
| 10,000 | 50,000,000 |
| 100,000 | 5,000,000,000 |
Ten times more input means about a hundred times more work. That is the pattern to recognise: a loop over the data with a list search inside it.
The fix: use a set for membership
def dedup(events: list[str]) -> list[str]:
seen = set()
out = []
for event in events:
if event not in seen: # hash lookup, O(1) on average
seen.add(event)
out.append(event)
return out
A set stores items in a hash table. To check event in seen, Python
computes the hash of event and looks in one place, so the cost does not
grow with the size of the set. The loop now does a constant amount of work
per event: O(n) in total.
The output list out keeps the order, because items are appended in the
order they first appear. The set is only used to answer "have I seen this?".
The one-line version: dict.fromkeys
Since Python 3.7, dictionaries keep insertion order. dict.fromkeys builds a
dict with each item as a key, and duplicate keys are ignored:
def dedup(events: list[str]) -> list[str]:
return list(dict.fromkeys(events))
dedup(["a", "b", "a", "c", "b", "d"])
# ['a', 'b', 'c', 'd']
This is also O(n) and is usually the shortest correct answer.
Why not list(set(items))?
list(set(["a", "b", "a", "c"]))
# order is not guaranteed, e.g. ['c', 'a', 'b']
list(set(...)) removes duplicates fast, but a set has no order. If the
order of the result does not matter, it is fine. If it does - events,
log lines, user choices - use the set-plus-list loop or dict.fromkeys.
Measure before and after
time python3 -c "from dedup import dedup; print(len(dedup([f'e{i%10000}' for i in range(20000)])))"
With the list version this takes seconds. With the set version it prints
10000 almost at once. Also check the edge cases, because a faster function
that returns a different result is a new bug:
assert dedup(["a", "b", "a", "c", "b", "d"]) == ["a", "b", "c", "d"]
assert dedup([]) == []
assert dedup(["x"]) == ["x"]
Items that are not hashable
Sets and dict keys need hashable items. Strings, numbers and tuples work.
Lists and dicts do not - set().add([1, 2]) raises TypeError: unhashable
type: 'list'. To dedup a list of dicts, build a hashable key from the fields
that define "the same":
def dedup_records(records):
seen = set()
out = []
for r in records:
key = (r["user_id"], r["event_type"], r["timestamp"])
if key not in seen:
seen.add(key)
out.append(r)
return out
The wider lesson
The same problem appears in many forms: if user_id in allowed_ids_list
inside a loop, list.index() in a loop, or a nested loop to find matches
between two lists. If you search a collection many times, build a set or a
dict once and look up in that instead.
Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.
Start this project - freeWhat you'll practice
- Recognising a list membership check inside a loop as O(n^2)
- Replacing the list with a set for O(1) average lookups
- Removing duplicates while keeping order with dict.fromkeys
- Timing the code before and after and testing edge cases
FAQ
How do I remove duplicates from a list in Python and keep the order?
Use list(dict.fromkeys(items)), or loop over the items and keep a set of values you have already seen. Both keep the first occurrence of each item in the original order and run in O(n).
Why is "in" slow on a list but fast on a set?
A list check compares the value with each element in turn, so it is O(n). A set uses a hash table and looks in one place, so it is O(1) on average.
Does list(set(x)) keep the order?
No. A set has no defined order, so the result can come back in any order. Use dict.fromkeys if the order matters.
How do I remove duplicate dicts from a list in Python?
Dicts are not hashable, so you cannot put them in a set directly. Build a tuple key from the fields that identify a record, keep a set of those keys, and append a record only when its key is new.
Keep learning
Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.
Start this project - free