Dictionary (Key-Value Pairs)
A List answers "what's at position 5?". A Dictionary answers "what's the value for this key?" — a username, a product code, a country. It maps unique keys to values and finds them almost instantly, which makes it the go-to structure for lookups, caches, and counting. It's one of the most useful collections you'll ever learn.
Learn Dictionary (Key-Value Pairs) in our free C# course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.
Part of the free C# course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
A dictionary is exactly like a physical dictionary or phone book . You don't read every page to find "aardvark" — you jump straight to the word (the key ) and read its definition (the value ). Each word appears once, and lookup is instant because the book is organised for it. That's why C# borrowed the name: unique keys, fast lookups, one definition per word.
1. Creating & Using a Dictionary
Declare a dictionary with both types: Dictionary<string, string> maps text keys to text values. Add entries with Add or the indexer, read with dict[key] , and check the size with Count . The indexer overwrites an existing key; Add throws if the key is already present. Iterate the pairs with foreach over KeyValuePair .
2. Reading Safely
The biggest beginner trap: reading a key that isn't there throws KeyNotFoundException . The two safe approaches are ContainsKey (check, then read) and TryGetValue (check and fetch in one lookup). Prefer TryGetValue — it's faster and reads cleanly.
Your turn. Build a small phone book — add a contact and look one up safely.
3. The Counting Pattern
Counting occurrences is the single most common dictionary task — tallying votes, word frequencies, items in a cart. The idea: for each item, if the key exists add one, otherwise start it at one. This pattern shows up constantly, so it's worth committing to memory.
🔎 Deep Dive: GetValueOrDefault
GetValueOrDefault(key) returns the value if the key exists, or a fallback (the type's default, or one you supply) if it doesn't — never throwing. It collapses the whole counting pattern into a single elegant line: tally[c] = tally.GetValueOrDefault(c) + 1; .
A note on ordering: a standard Dictionary makes no promise about iteration order. If you need keys sorted, use a SortedDictionary , or sort the keys yourself when you enumerate.
🧩 Reorder Challenge
These six lines count letters in "moon" and print the count for 'o' . Order them so it prints o appears 2 times .
Why: word must exist before the loop reads it, and the tally dictionary must exist before the loop fills it. The loop must finish counting before the final WriteLine reads tally['o'] , which by then is 2 .
🧠 Quick Recall
5 — assigning with the indexer to an existing key overwrites it, so the second assignment wins.
False 0 — key "y" is absent, so TryGetValue returns false and sets v to its default, 0 .
-1 — key "m" doesn't exist, so GetValueOrDefault returns the supplied fallback of -1 .
Pro Tips
- 💡 TryGetValue over ContainsKey + indexer — it does the check and fetch in one lookup.
- 💡 Indexer to overwrite, Add to insist on a new key. Pick based on whether a duplicate is a bug.
- 💡 GetValueOrDefault makes counting a one-liner and avoids the if/else.
- 💡 Don't rely on order. A Dictionary isn't ordered; use SortedDictionary if you need sorted keys.
Common Errors (and the fix)
- "KeyNotFoundException": you read a key that isn't there with the indexer. Use TryGetValue or check ContainsKey .
- "ArgumentException: an item with the same key has already been added": Add hit a duplicate. Use the indexer to overwrite instead.
- Relying on insertion order: a Dictionary may iterate in any order. Sort explicitly if order matters.
- "The given key was not present" when removing: harmless — Remove simply returns false for a missing key.
Frequently Asked Questions
Mini-Challenge: Vote Tally
Count votes per colour with a dictionary, then find the winner. The outline is in the comments — fill in the body.
🎉 Lesson Complete
- ✅ Dictionary<TKey, TValue> maps unique keys to values for fast lookup
- ✅ Indexer overwrites; Add throws on a duplicate key
- ✅ Read safely with TryGetValue or ContainsKey — the indexer throws on a missing key
- ✅ Iterate pairs with foreach over KeyValuePair
- ✅ GetValueOrDefault powers the counting/tally pattern in one line
- ✅ Next lesson: Properties — control access to an object's data
Practice quiz
What does a Dictionary<TKey, TValue> map?
- Positions to values
- Unique keys to values
- Values to keys only
- Indexes to a sorted list
Answer: Unique keys to values. A Dictionary maps a unique key to a value, and finds the value by key almost instantly.
What happens if you call Add with a key that already exists?
- It overwrites the value silently
- It returns false
- It throws ArgumentException
- It adds a duplicate entry
Answer: It throws ArgumentException. Add with a duplicate key throws ArgumentException; use the indexer dict[key] = value to overwrite instead.
What does the indexer dict[key] = value do when the key already exists?
- Throws an exception
- Overwrites the existing value silently
- Ignores the assignment
- Adds a second entry
Answer: Overwrites the existing value silently. Assigning with the indexer overwrites an existing key's value silently — handy when overwriting is intended.
What happens when you read a missing key with the indexer dict[key]?
- Returns null
- Returns the type's default
- Throws KeyNotFoundException
- Returns false
Answer: Throws KeyNotFoundException. Reading a key that isn't present with the indexer throws KeyNotFoundException — use TryGetValue or ContainsKey instead.
Why is TryGetValue preferred over ContainsKey + indexer?
- It sorts the dictionary
- It checks and fetches in a single lookup
- It never returns false
- It allows duplicate keys
Answer: It checks and fetches in a single lookup. TryGetValue does the existence check and the fetch in one lookup, so it's faster and cleaner than checking with ContainsKey then reading.
When a key is absent, what does TryGetValue do to the out parameter?
- Leaves it unassigned
- Sets it to the type's default and returns false
- Throws
- Sets it to null always
Answer: Sets it to the type's default and returns false. TryGetValue returns false and sets the out value to the type's default (e.g. 0 for int) — it never throws.
Does a standard Dictionary guarantee iteration order?
- Yes, insertion order
- Yes, sorted by key
- No, it makes no promise about order
- Only for string keys
Answer: No, it makes no promise about order. A standard Dictionary makes no promise about iteration order; use SortedDictionary for keys sorted by key.
What does GetValueOrDefault("m", -1) return when key "m" is absent?
- 0
- null
- -1
- Throws KeyNotFoundException
Answer: -1. GetValueOrDefault returns the supplied fallback (-1 here) for a missing key, never throwing.
Which one-liner implements the counting pattern with GetValueOrDefault?
counts[c] = counts.GetValueOrDefault(c) + 1; starts missing keys at 0 then increments — the whole tally pattern in one line.
What type do you get when you iterate a Dictionary with foreach?
- A KeyValuePair<TKey, TValue> per entry
- Just the keys
- Just the values
- A tuple of indexes
Answer: A KeyValuePair<TKey, TValue> per entry. Iterating a Dictionary yields a KeyValuePair<TKey, TValue> for each entry, exposing .Key and .Value.
Continue this course
- Previous: List<T>
- Next: Properties & Auto-Properties