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.
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: 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.
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.
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 .
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 .
Count votes per colour with a dictionary, then find the winner. The outline is in the comments — fill in the body.
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.