HashSet
C#
Data Structures
Programming
.NET

When should I use the HashSetT type?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Use HashSet<T> when uniqueness and fast membership checks are primary requirements. It is often the right choice for deduplication and set algebra logic in .NET applications. The tradeoff is that HashSet<T> does not offer index-based access or stable order guarantees like List<T>.

What HashSet<T> Is Built For

HashSet<T> stores each value once based on equality semantics and hash codes. Core operations such as Add, Contains, and Remove are typically constant time on average.

csharp
1using System;
2using System.Collections.Generic;
3
4public class HashSetBasics
5{
6    public static void Main()
7    {
8        var users = new HashSet<string>();
9
10        Console.WriteLine(users.Add("alice")); // true
11        Console.WriteLine(users.Add("alice")); // false
12        Console.WriteLine(users.Contains("alice")); // true
13    }
14}

If your code repeatedly checks existence in large collections, this can be a major performance gain over linear scans in lists.

Compare with List<T> and Dictionary<TKey,TValue>

Choose by behavior:

Use List<T> when:

  • order matters
  • duplicates are allowed
  • index access is required

Use HashSet<T> when:

  • duplicates are disallowed by design
  • membership checks are frequent
  • set operations are needed

Use Dictionary<TKey,TValue> when:

  • each item has a key-value mapping
  • key lookup is needed with associated payload

This comparison prevents using one type for all problems out of habit.

Set Operations Are a Strong Reason

HashSet<T> includes efficient operations for union, intersection, and difference.

csharp
1using System;
2using System.Collections.Generic;
3
4public class SetOperations
5{
6    public static void Main()
7    {
8        var requested = new HashSet<int> { 1, 2, 3, 4 };
9        var allowed = new HashSet<int> { 3, 4, 5 };
10
11        requested.IntersectWith(allowed);
12
13        foreach (var id in requested)
14        {
15            Console.WriteLine(id); // 3, 4
16        }
17    }
18}

Without HashSet<T>, equivalent logic often becomes nested loops with extra duplicate cleanup.

Equality Rules Must Be Correct

For custom types, HashSet<T> behavior depends on Equals and GetHashCode. Inconsistent implementations produce subtle bugs where duplicates slip through or distinct items collapse.

csharp
1using System;
2using System.Collections.Generic;
3
4public sealed class User : IEquatable<User>
5{
6    public int Id { get; }
7    public string Name { get; }
8
9    public User(int id, string name)
10    {
11        Id = id;
12        Name = name;
13    }
14
15    public bool Equals(User? other) => other is not null && Id == other.Id;
16    public override bool Equals(object? obj) => obj is User u && Equals(u);
17    public override int GetHashCode() => Id.GetHashCode();
18}
19
20public class EqualityDemo
21{
22    public static void Main()
23    {
24        var set = new HashSet<User>();
25        set.Add(new User(1, "alice"));
26        set.Add(new User(1, "alice-updated"));
27
28        Console.WriteLine(set.Count); // 1
29    }
30}

Define equality around domain identity, not incidental fields.

Memory and Ordering Considerations

HashSet<T> often uses more memory than list structures because it maintains hash buckets. For tiny collections, this overhead may not be worth it. Also, enumeration order should not be treated as business logic contract.

If you need deterministic presentation order, combine HashSet<T> for membership checks with a separate ordered projection step.

Practical Use Cases

Great fits include:

  • request deduplication keys
  • permission intersection between user scopes and resource requirements
  • visited-node tracking in graph traversal
  • uniqueness checks in streaming ingestion pipelines

In these scenarios, intent clarity and runtime efficiency both improve.

Common Pitfalls

  • Using HashSet<T> when ordered output is a strict requirement.
  • Forgetting to implement correct equality for custom types.
  • Using list scans for membership checks in hot paths.
  • Assuming hash-based collections are always best for tiny data sets.
  • Encoding business logic that depends on incidental iteration order.

Summary

  • Use HashSet<T> for uniqueness and fast membership checks.
  • Prefer it when set operations are central to the problem.
  • Implement equality and hash code rules carefully for custom types.
  • Do not rely on hash set enumeration order for business behavior.
  • Choose collection type based on access pattern, not familiarity.

Course illustration
Course illustration

All Rights Reserved.