ListT.Contains is very slow?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
List<T>.Contains() is O(n) because it performs a linear search, comparing each element sequentially. For small lists (under ~100 items), this is fast enough. For large lists or frequent lookups, switch to HashSet<T> which provides O(1) average-time Contains(). If you need both ordered access and fast lookup, maintain a HashSet<T> alongside the List<T>, or use SortedSet<T> for O(log n) lookups with sorted iteration.
Why List Contains Is Slow
List<T>.Contains() calls EqualityComparer<T>.Default.Equals() on each element from start to end:
For a list with 1 million items, the worst case checks all 1 million elements.
Fix: Use HashSet for Fast Lookups
HashSet<T> uses a hash table for O(1) average-time lookups:
Benchmark Comparison
Collection Comparison Table
| Collection | Contains | Add | Order | Duplicates |
List<T> | O(n) | O(1) amortized | Insertion order | Yes |
HashSet<T> | O(1) | O(1) | No order | No |
SortedSet<T> | O(log n) | O(log n) | Sorted | No |
Dictionary<K,V> | O(1) by key | O(1) | No order | Keys unique |
SortedList<K,V> | O(log n) | O(n) | Sorted by key | Keys unique |
Pattern: List + HashSet Together
When you need both ordered access and fast lookups:
Using LINQ Contains Efficiently
This is especially important when filtering a large collection against another large collection.
Custom Equality
For custom objects, implement IEquatable<T> and override GetHashCode:
Without GetHashCode, HashSet<T> falls back to reference equality and loses its O(1) performance.
Binary Search on Sorted Lists
If the list is sorted, use BinarySearch for O(log n) lookups:
Common Pitfalls
- Calling
Containsin a loop on a large list: If you checklist.Contains(x)for each element of another collection, the total complexity is O(n*m). Convert the lookup collection to aHashSet<T>once (O(n)) and then each lookup is O(1). - Not implementing
GetHashCodefor custom types in HashSet:HashSet<T>relies onGetHashCodeto distribute items into buckets. IfGetHashCodealways returns the same value, all items land in one bucket and lookups degrade to O(n). Always implementGetHashCodeto matchEquals. - Using
HashSetwhen order matters:HashSet<T>does not preserve insertion order. If you need both fast lookup and ordered access, maintain aList<T>andHashSet<T>side by side. - Premature optimization for small collections: For lists under ~100 elements,
List<T>.Containsis fast enough and avoids the overhead of hash computation. Profile before switching toHashSet<T>. - Assuming
Dictionary.ContainsValueis fast:Dictionary<K,V>.ContainsKeyis O(1), butContainsValueis O(n) because values are not hashed. If you need fast value lookups, create a reverseDictionary<V,K>or aHashSet<V>.
Summary
List<T>.Contains()is O(n) — it scans every element sequentiallyHashSet<T>.Contains()is O(1) — use it for frequent lookups on large collections- Convert a list to
HashSet<T>before usingContainsin loops or LINQ queries - For sorted data, use
List<T>.BinarySearch()for O(log n) lookups - Implement
IEquatable<T>andGetHashCodefor custom types to enable efficient hashing
Related reading
- LLDB Swift Casting Raw Address into Usable Type
- Load balancing the load balancers
- Load jQuery Mobile script asynchronously?
- loading js files and other dependent js files asynchronously
- Literal suffix for byte in .NET?
- Load model with ML.NET saved with keras
- Loading Model only once in fastAPI
- Loading SavedModel is a lot slower than loading a tf.train.Saver checkpoint

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 courseTrack 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.