duplicate detection
list processing
algorithm optimization
data structures
Python programming

Efficiently finding duplicates in a list

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Finding duplicates efficiently depends on what you actually need: whether you want to know if any duplicate exists, list the repeated values, count frequencies, or preserve the original order of repeated items. The naive nested-loop solution works, but it is quadratic and quickly becomes the wrong choice on real data. In Python, hash-based approaches using set or Counter are usually the right starting point.

Fastest General Check: Use a set

If you only need to know whether duplicates exist, compare the list length to the size of a set built from it.

python
1values = [4, 2, 9, 2, 7]
2
3has_duplicates = len(values) != len(set(values))
4print(has_duplicates)

This is typically O(n) average time because set membership and insertion are hash-based.

The tradeoff is that this only works for hashable values such as numbers, strings, and tuples of hashable elements.

Get the Duplicate Values

If you want the repeated elements themselves, track what you have seen and what has repeated.

python
1values = [4, 2, 9, 2, 7, 4, 4]
2
3seen = set()
4dupes = set()
5
6for value in values:
7    if value in seen:
8        dupes.add(value)
9    else:
10        seen.add(value)
11
12print(dupes)

This returns the duplicated values once each. It does not preserve the order in which duplicates first appeared.

Preserve Encounter Order

If output order matters, keep a list for duplicates and a set to avoid appending the same duplicate more than once.

python
1values = [4, 2, 9, 2, 7, 4, 4]
2
3seen = set()
4already_reported = set()
5duplicates_in_order = []
6
7for value in values:
8    if value in seen and value not in already_reported:
9        duplicates_in_order.append(value)
10        already_reported.add(value)
11    seen.add(value)
12
13print(duplicates_in_order)

This pattern is helpful in validation messages where you want deterministic, human-readable output.

Count Frequency with Counter

If you need counts, collections.Counter is usually the cleanest solution.

python
1from collections import Counter
2
3values = ["a", "b", "a", "c", "b", "a"]
4counts = Counter(values)
5
6duplicates = {item: count for item, count in counts.items() if count > 1}
7print(duplicates)

This is often the best answer for data-cleaning tasks because it tells you not only what repeated, but how often.

Sorting-Based Alternative

If the data is not hashable or you want a solution with lower auxiliary memory in some contexts, sort first and compare adjacent elements.

python
1values = [4, 2, 9, 2, 7, 4]
2sorted_values = sorted(values)
3
4duplicates = []
5for i in range(1, len(sorted_values)):
6    if sorted_values[i] == sorted_values[i - 1]:
7        duplicates.append(sorted_values[i])
8
9print(duplicates)

This is O(n log n) due to sorting. It can be reasonable when hashing is unavailable or when sorted output is desirable anyway.

Unhashable Items Such as Lists or Dicts

If the list contains unhashable items, direct set or Counter use will fail.

For example:

python
values = [[1, 2], [3, 4], [1, 2]]

You can convert list elements to tuples if that preserves the meaning you need:

python
1values = [[1, 2], [3, 4], [1, 2]]
2
3normalized = [tuple(v) for v in values]
4duplicates = {item for item, count in Counter(normalized).items() if count > 1}
5print(duplicates)

For dictionaries or more complex nested objects, you may need a custom normalization strategy.

Streaming Large Inputs

If the data arrives as a stream and you cannot store everything, a seen-set approach still works for “first duplicate encountered” style detection:

python
1def first_duplicate(stream):
2    seen = set()
3    for item in stream:
4        if item in seen:
5            return item
6        seen.add(item)
7    return None
8
9print(first_duplicate([10, 20, 30, 20, 40]))

This lets you short-circuit early instead of processing the entire dataset.

When the Naive Approach Is Still Fine

For tiny lists in one-off scripts, a nested loop or repeated count() call may be acceptable. The point is not that every duplicate search needs advanced optimization. The point is that once input size grows, quadratic work becomes avoidable and expensive.

Common Pitfalls

The biggest mistake is using nested loops on large lists when a hash-based approach would be linear on average. Another is forgetting that sets discard order, which may matter if you need deterministic duplicate reporting. Developers also often assume every element is hashable and then hit errors with lists or dictionaries. Finally, Counter is excellent for counts, but it is more work than necessary if all you need is a quick yes or no duplicate check.

Summary

  • Use len(values) != len(set(values)) for the fastest simple duplicate-existence check.
  • Use a seen-set pattern to collect duplicate values efficiently.
  • Use Counter when frequency counts matter.
  • Use sorting if hashing is unavailable or sorted duplicate output is useful.
  • Normalize unhashable items first if you need hash-based duplicate detection.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.