When to Use HashSet vs List in C#
HashSet<T> is an unordered collection optimized for fast lookups, uniqueness enforcement, and set operations. List<T> is an ordered collection that allows duplicates and provides index-based access.ess.
Use HashSet<T> when you need to check for membership frequently (Contains), enforce uniqueness, or perform set operations like union/intersection. Use List<T> when order matters, you need indexed access, or duplicates are allowed.
HashSet has O(1) Contains/Add/Remove operations, while List has O(n) for Contains and Remove. List provides O(1) indexed access, which HashSet doesn't support.
Setup and Basic Comparison
C# Example Code
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
// List - ordered, allows duplicates
Console.WriteLine("=== List<T> - Ordered with duplicates ===");
List<string> namesList = new List<string> { "Alice", "Bob", "Alice", "Charlie" };
namesList.Add("Bob"); // Duplicates allowed
Console.WriteLine($"List count: {namesList.Count}");
Console.WriteLine("List contents:");
foreach (var name in namesList)
{
Console.WriteLine($" {name}");
}
// HashSet - unordered, unique items only
Console.WriteLine("\n=== HashSet<T> - Unique items only ===");
HashSet<string> namesSet = new HashSet<string> { "Alice", "Bob", "Alice", "Charlie" };
namesSet.Add("Bob"); // Duplicate not added
Console.WriteLine($"HashSet count: {namesSet.Count}");
Console.WriteLine("HashSet contents:");
foreach (var name in namesSet)
{
Console.WriteLine($" {name}");
}Membership Testing and Index Access
C# Example Code
// Fast membership testing with HashSet
Console.WriteLine("\n=== Membership testing ===");
Console.WriteLine($"List contains 'Alice': {namesList.Contains("Alice")}");
Console.WriteLine($"HashSet contains 'Alice': {namesSet.Contains("Alice")}");
// Index access - List only
Console.WriteLine("\n=== Index access (List only) ===");
Console.WriteLine($"First item in list: {namesList[0]}");
Console.WriteLine($"Last item in list: {namesList[namesList.Count - 1]}");
// namesSet[0]; // Error: HashSet doesn't support indexingSet Operations with HashSet
C# Example Code
// Set operations - HashSet speciality
Console.WriteLine("\n=== Set operations ===");
HashSet<int> setA = new HashSet<int> { 1, 2, 3, 4, 5 };
HashSet<int> setB = new HashSet<int> { 4, 5, 6, 7, 8 };
// Union - all unique items from both sets
HashSet<int> union = new HashSet<int>(setA);
union.UnionWith(setB);
Console.WriteLine($"Union: {string.Join(", ", union)}");
// Intersection - common items
HashSet<int> intersection = new HashSet<int>(setA);
intersection.IntersectWith(setB);
Console.WriteLine($"Intersection: {string.Join(", ", intersection)}");
// Difference - items in A but not in B
HashSet<int> difference = new HashSet<int>(setA);
difference.ExceptWith(setB);
Console.WriteLine($"Difference (A - B): {string.Join(", ", difference)}");
// Symmetric difference - items in either A or B but not both
HashSet<int> symmetricDiff = new HashSet<int>(setA);
symmetricDiff.SymmetricExceptWith(setB);
Console.WriteLine($"Symmetric Difference: {string.Join(", ", symmetricDiff)}");Removing Duplicates
C# Example Code
// Removing duplicates from List using HashSet
Console.WriteLine("\n=== Removing duplicates ===");
List<int> numbersWithDupes = new List<int> { 1, 2, 2, 3, 3, 3, 4, 4, 5 };
HashSet<int> uniqueNumbers = new HashSet<int>(numbersWithDupes);
Console.WriteLine($"Original list: {string.Join(", ", numbersWithDupes)}");
Console.WriteLine($"Unique items: {string.Join(", ", uniqueNumbers)}");Performance Comparison
C# Example Code
// Performance comparison
Console.WriteLine("\n=== Performance comparison ===");
PerformanceTest();
static void PerformanceTest()
{
const int size = 10000;
const int lookups = 1000;
// Populate collections
List<int> list = new List<int>();
HashSet<int> set = new HashSet<int>();
for (int i = 0; i < size; i++)
{
list.Add(i);
set.Add(i);
}
Stopwatch sw = new Stopwatch();
// List Contains - O(n)
sw.Start();
for (int i = 0; i < lookups; i++)
{
bool exists = list.Contains(size / 2);
}
sw.Stop();
long listTime = sw.ElapsedMilliseconds;
// HashSet Contains - O(1)
sw.Restart();
for (int i = 0; i < lookups; i++)
{
bool exists = set.Contains(size / 2);
}
sw.Stop();
long setTime = sw.ElapsedMilliseconds;
Console.WriteLine($"List.Contains: {listTime}ms");
Console.WriteLine($"HashSet.Contains: {setTime}ms");
Console.WriteLine($"HashSet is ~{(listTime > 0 ? listTime / Math.Max(setTime, 1) : 0)}x faster for lookups");
}When to Use Each
Use List<T> when:
- Order matters
- Need indexed access
- Duplicates are allowed/needed
- Need to sort the collection
Use HashSet<T> when:
- Need to check membership frequently
- Must enforce uniqueness
- Performing set operations
- Order doesn't matter