C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
185: HashSet<T> and SortedSet<T>
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.
There are no comments for now.