Skip to Content
Course content

185: HashSet<T> and SortedSet<T>

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this pattern countless times in code reviews: a developer uses a List<T> to track a collection of unique items, like a list of user IDs currently active in a game lobby. They'll write a check like if (!users.Contains(userId)) { users.Add(userId); }. On a small scale, with ten or twenty users, this is completely invisible. It works perfectly. But as that list grows to thousands, your application starts to crawl. This is the classic "naive" approach to uniqueness, and it's where we need to talk about HashSet<T> and SortedSet<T>.

The hidden cost of searching a list

The problem with the List.Contains() approach is that it performs a linear search. To figure out if a user ID is already there, C# has to start at the beginning of the list and check every single element until it finds a match or hits the end. In big-O notation, we call this O(n). If you have 10,000 users, you might be performing 10,000 checks just to add one new person. If you're doing this in a loop to import a large dataset, you've accidentally created a performance bottleneck that will be a nightmare to debug later.

// The slow way
List<string> activeUsers = new List<string>();
foreach (var user in incomingUsers)
{
    if (!activeUsers.Contains(user)) // O(n) search every time!
    {
        activeUsers.Add(user);
    }
}

Instant lookups with HashSet

This is where HashSet<T> comes in. Instead of a sequence, a hash set uses a hash table. When you ask a HashSet if it contains an item, it doesn't scan the whole collection; it computes a hash code for the object and jumps almost directly to the memory location where that object would be. This turns an O(n) operation into an O(1) operation. Whether you have ten items or ten million, the time it takes to check for existence is virtually the same.

The trade-off is simple: you lose the concept of an index. You can't ask for "the fifth element" of a HashSet because the items aren't stored in any guaranteed order. If you don't need to access items by their position, there is almost no reason to use a List for uniqueness.

// The professional way
HashSet<string> activeUsers = new HashSet<string>();
foreach (var user in incomingUsers)
{
    // Add() returns false if the item already exists, 
    // so you don't even need the 'if' check.
    activeUsers.Add(user); 
}

When order actually matters

Now, sometimes you need the best of both worlds: you need to ensure every item is unique, but you also need them to stay sorted—perhaps for a leaderboard or a directory that must be displayed alphabetically. You might be tempted to use a HashSet and then call .OrderBy() every time you need to read it, but that's inefficient if the set changes frequently.

SortedSet<T> solves this. It maintains the elements in a sorted tree structure. While it's not quite as fast as a HashSet (it's O(log n) rather than O(1)), it's still vastly superior to sorting a list manually. I usually reach for SortedSet when the "sortedness" is a core requirement of the data's lifecycle, not just a one-time requirement for a UI view.

One quick warning: because SortedSet needs to know how to sort your items, the type T must either implement IComparable or you must provide a custom IComparer in the constructor. If you try to put a custom class in there without a comparison rule, C# will throw an exception the moment you try to add an item.




📋 Practical Task

Building a Duplicate-Free Guest List for a Virtual Event

You are building a check-in system for a virtual conference. Guests are checking in via an API, but due to network glitches, some guests are sending their check-in request multiple times. You need to process a raw list of email addresses and produce a final, unique list of guests sorted alphabetically.

  • Create a List<string> containing several duplicate emails (e.g., "alice@example.com", "bob@example.com", "alice@example.com", "charlie@example.com", "bob@example.com").
  • Initialize a SortedSet<string> to handle the check-in process.
  • Iterate through the raw list and add each email to the set.
  • Print the final count of unique guests and then iterate through the set to print the sorted list of emails to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.