Dictionaries and sets
Look things up by key instantly with Dictionary, and track unique items with HashSet.
12 min read
You will learn
- Store and retrieve values by key with Dictionary<TKey, TValue>
- Read safely with TryGetValue and ContainsKey
- Count and group data with a dictionary
- Use HashSet<T> for uniqueness and set operations
Lookup by key
A list answers "what's at position 3?". Often you need to ask "what's the price of coffee?" or "which user has the id 42?". Searching a list for that means checking every item. A dictionary maps keys to values and finds any key almost instantly.
var prices = new Dictionary<string, decimal>
{
["coffee"] = 3.50m,
["tea"] = 2.80m,
};
prices["juice"] = 4.20m; // add (or overwrite)
Console.WriteLine(prices["coffee"]); // 3.50
Console.WriteLine(prices.Count); // 3Keys are unique; values can repeat. Keys and values can be any type: Dictionary<int, User>, Dictionary<string, List<Order>>…
Reading safely
Reading a missing key with the indexer throws KeyNotFoundException. When a key might be absent:
if (prices.TryGetValue("water", out decimal price))
Console.WriteLine(price);
else
Console.WriteLine("Not on the menu");
decimal p = prices.GetValueOrDefault("water"); // 0 if missing
decimal q = prices.GetValueOrDefault("water", 1.00m); // custom fallback
bool hasTea = prices.ContainsKey("tea");Adding, updating, removing
| Operation | Effect |
|---|---|
dict[key] = value | Add, or overwrite if the key exists |
dict.Add(key, value) | Add — throws if the key exists |
dict.TryAdd(key, value) | Add only if missing (returns bool) |
dict.Remove(key) | Remove (returns bool) |
Looping
Each entry is a KeyValuePair<TKey, TValue>, which you can deconstruct:
foreach (var (item, cost) in prices)
{
Console.WriteLine($"{item}: {cost:F2}");
}
foreach (string key in prices.Keys) { ... }
foreach (decimal value in prices.Values) { ... }Note
Dictionary doesn't promise any particular order. If you need keys sorted, use SortedDictionary<TKey, TValue> or sort when displaying.
The counting pattern
Counting occurrences is one of the most common dictionary tasks:
var counts = new Dictionary<char, int>();
foreach (char c in "mississippi")
{
counts[c] = counts.GetValueOrDefault(c) + 1;
}
// m:1, i:4, s:4, p:2The same shape works for grouping: Dictionary<string, List<Order>> ordersByCustomer.
How it's so fast: hashing
A dictionary calls GetHashCode() on each key to compute which internal "bucket" it belongs to, then jumps straight there. Lookups take roughly the same time with 10 entries or 10 million — O(1) on average, versus O(n) for searching a list.
That's also why keys must not change after being added, and why types used as keys need sensible Equals and GetHashCode — which records, strings and numbers already have.
HashSet: just the keys
Sometimes you only care whether something is in a collection. A HashSet<T> stores unique items with the same fast lookup:
var visited = new HashSet<string>();
visited.Add("Paris");
visited.Add("Rome");
bool added = visited.Add("Paris"); // false — already there
Console.WriteLine(visited.Contains("Rome")); // true, and fast
Console.WriteLine(visited.Count); // 2Sets support mathematical set operations:
var a = new HashSet<int> { 1, 2, 3 };
var b = new HashSet<int> { 2, 3, 4 };
a.IntersectWith(b); // a = { 2, 3 } — in both
// a.UnionWith(b); // { 1, 2, 3, 4 } — in either
// a.ExceptWith(b); // { 1 } — in a but not bRemoving duplicates from a list is a one-liner: var unique = new HashSet<string>(names);
Try it
C# runtime not loaded
Key takeaways
Dictionary<TKey, TValue>maps unique keys to values with near-instant lookup.- Use
TryGetValue/GetValueOrDefaultwhen a key may be missing; the indexer throws. dict[key] = valueadds or overwrites;GetValueOrDefault(key) + 1is the counting idiom.HashSet<T>stores unique items and supports fastContainsand set operations.
Exercise
WordCounts counts how often each word appears (case-insensitive, words separated by spaces). MostFrequent returns the most common word. HasDuplicates reports whether any number appears twice. CommonCount returns how many DISTINCT numbers appear in both arrays.
C# runtime not loaded
Hint 1
WordCounts: split with text.ToLower().Split(' ', StringSplitOptions.RemoveEmptyEntries), then counts[word] = counts.GetValueOrDefault(word) + 1.
Hint 2
MostFrequent: build the counts, then loop over the pairs (foreach (var (word, count) in counts)) keeping the best.
Hint 3
HashSet.Add returns false when the item is already there — perfect for HasDuplicates. For CommonCount, look at IntersectWith.
Show solution
var counts = WordCounts("the cat and the hat");
Console.WriteLine(counts.Count);
Dictionary<string, int> WordCounts(string text)
{
var counts = new Dictionary<string, int>();
foreach (var word in text.ToLower().Split(' ', StringSplitOptions.RemoveEmptyEntries))
{
counts[word] = counts.GetValueOrDefault(word) + 1;
}
return counts;
}
string MostFrequent(string text)
{
string best = "";
int bestCount = 0;
foreach (var (word, count) in WordCounts(text))
{
if (count > bestCount)
{
best = word;
bestCount = count;
}
}
return best;
}
bool HasDuplicates(int[] numbers)
{
var seen = new HashSet<int>();
foreach (var n in numbers)
{
if (!seen.Add(n)) return true;
}
return false;
}
int CommonCount(int[] a, int[] b)
{
var set = new HashSet<int>(a);
set.IntersectWith(b);
return set.Count;
}Quiz
4 questions · 3 correct to pass1.What happens when you read `dict["missing"]` for a key that isn't there?
2.What does `dict["a"] = 5;` do if "a" already exists?
3.Why are dictionary lookups fast even with millions of entries?
4.What does `new HashSet<int> { 1, 2, 2, 3 }.Count` return?
To complete this lesson
- ○ Solve the exercise
- ○ Pass the quiz (3/4 correct)