Back to CourseIntermediate4 min read
C Sharp /

HashSet<T>

Keep unique items with HashSet, test membership in constant time, remove duplicates and perform union, intersection and difference operations.

Bishal Biswas

Bishal Biswas

Self Learner

Published on: April 13, 2026
Last updated: September 5, 2026
Intermediate Topic

A collection with no duplicates

A HashSet<T> stores items with two promises: every item appears once, and checking whether an item is present is fast no matter how many items there are.

var tags = new HashSet<string>();
tags.Add("csharp");
tags.Add("dotnet");
tags.Add("csharp");   // ignored, already present

Console.WriteLine(tags.Count);   // 2

Add returns true if the item was new and false if it was already there, which is handy for detecting duplicates.

bool added = tags.Add("dotnet");
Console.WriteLine(added);   // False

Membership tests

This is what sets are for:

var banned = new HashSet<string> { "spam", "scam", "fake" };

string word = "scam";
if (banned.Contains(word))
{
    Console.WriteLine("Blocked");
}

Contains on a list scans every item. On a set it hashes the value and jumps straight to it. With a thousand banned words and a million messages, the difference is enormous.

Removing duplicates

The quickest way to deduplicate a list:

var numbers = new List<int> { 1, 2, 2, 3, 3, 3, 4 };
var unique = new HashSet<int>(numbers);

Console.WriteLine(unique.Count);                    // 4
Console.WriteLine(string.Join(" ", unique));        // 1 2 3 4

Or in one line with LINQ: numbers.Distinct().ToList().

No indexes, no order

A set has no [0]. You cannot ask for “the third item” because a set does not promise any order. Iterate with foreach when you need to see everything:

foreach (int n in unique)
{
    Console.Write(n + " ");
}

If you need uniqueness and sorted order, use SortedSet<T>.

Set operations

Sets shine when you compare groups of things. Each of these modifies the set in place:

var a = new HashSet<int> { 1, 2, 3, 4 };
var b = new HashSet<int> { 3, 4, 5, 6 };

a.UnionWith(b);          // everything in either: 1 2 3 4 5 6
var a = new HashSet<int> { 1, 2, 3, 4 };
var b = new HashSet<int> { 3, 4, 5, 6 };

a.IntersectWith(b);      // only in both: 3 4
var a = new HashSet<int> { 1, 2, 3, 4 };
var b = new HashSet<int> { 3, 4, 5, 6 };

a.ExceptWith(b);         // in a but not b: 1 2
var a = new HashSet<int> { 1, 2, 3, 4 };
var b = new HashSet<int> { 3, 4, 5, 6 };

a.SymmetricExceptWith(b);   // in one but not both: 1 2 5 6

Because they modify the set, copy first if you need the original afterwards: new HashSet<int>(a).

Comparison queries

These return a bool and leave both sets alone:

var required = new HashSet<string> { "read", "write" };
var granted = new HashSet<string> { "read", "write", "delete" };

Console.WriteLine(required.IsSubsetOf(granted));     // True
Console.WriteLine(granted.IsSupersetOf(required));   // True
Console.WriteLine(required.Overlaps(granted));       // True
Console.WriteLine(required.SetEquals(granted));      // False

IsSubsetOf is a clean way to ask “does this user have every permission we need?”.

A practical example: visited tracking

Sets are the standard tool for remembering what you have already processed:

var visited = new HashSet<string>();
var queue = new Queue<string>(new[] { "home", "about", "home", "contact", "about" });

while (queue.Count > 0)
{
    string page = queue.Dequeue();
    if (!visited.Add(page)) continue;   // already seen, skip
    Console.WriteLine($"Crawling {page}");
}
// Crawling home, Crawling about, Crawling contact

The Add return value does the check and the insert in one step.

Your own types in a set

Like dictionary keys, set items are located by GetHashCode and Equals. For a class you must override both, or two objects with the same data will be treated as different. Records do this for you:

record Point(int X, int Y);

var points = new HashSet<Point> { new(1, 2), new(1, 2), new(3, 4) };
Console.WriteLine(points.Count);   // 2

Case insensitive sets

var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Asha" };
Console.WriteLine(names.Contains("ASHA"));   // True

Quick recap

  • A HashSet<T> holds each item once and checks membership in constant time.
  • Add returns false for duplicates, which doubles as a “seen before” check.
  • Build a set from a list to remove duplicates.
  • Sets have no index and no guaranteed order. Use SortedSet<T> for order.
  • UnionWith, IntersectWith and ExceptWith combine sets, and IsSubsetOf compares them.
  • Items need proper Equals and GetHashCode, which records provide automatically.
Program.cs

// HashSet<T> in C#
// Unique items only
var tags = new HashSet<string> { "csharp", "dotnet" };
Console.WriteLine(tags.Add("csharp"));        // False, already there
Console.WriteLine(tags.Add("linq"));          // True
Console.WriteLine(tags.Count);                // 3
// Fast membership check
var banned = new HashSet<string> { "spam", "scam" };
Console.WriteLine(banned.Contains("scam"));   // True
// Deduplicate a list
var numbers = new List<int> { 1, 2, 2, 3, 3, 3, 4 };
var unique = new HashSet<int>(numbers);
Console.WriteLine(string.Join(" ", unique));  // 1 2 3 4
// Set operations modify the set in place, so copy first
var a = new HashSet<int> { 1, 2, 3, 4 };
var b = new HashSet<int> { 3, 4, 5, 6 };
var union = new HashSet<int>(a); union.UnionWith(b);
var inter = new HashSet<int>(a); inter.IntersectWith(b);
var diff = new HashSet<int>(a);  diff.ExceptWith(b);
Console.WriteLine(string.Join(" ", union));   // 1 2 3 4 5 6
Console.WriteLine(string.Join(" ", inter));   // 3 4
Console.WriteLine(string.Join(" ", diff));    // 1 2
// Comparison queries
var required = new HashSet<string> { "read", "write" };
var granted = new HashSet<string> { "read", "write", "delete" };
Console.WriteLine(required.IsSubsetOf(granted));   // True
// Track visited items with the Add return value
var visited = new HashSet<string>();
foreach (string page in new[] { "home", "about", "home", "contact" })
{
    if (!visited.Add(page)) continue;         // skip repeats
    Console.WriteLine($"Visiting {page}");
}
// Records give value equality for free
record Point(int X, int Y);
var points = new HashSet<Point> { new(1, 2), new(1, 2) };
Console.WriteLine(points.Count);              // 1

Was this explanation helpful?

Your feedback helps us improve the content for everyone.