Lists, Dictionaries & Sets
Arrays have a fixed size, but real data keeps changing: passengers join a waiting list, products are added to a cart, words are counted in a message. .NET's collections handle this. The most important ones live in the System.Collections.Generic namespace, which new projects import automatically.
In this lesson you will use List<T> for an ordered, growing list, Dictionary<TKey, TValue> for looking things up by a key, HashSet<T> for unique items, and Queue<T> and Stack<T> for first-in-first-out and last-in-first-out order.
List<T>: a list that grows
List<T> is like an array that can grow and shrink. The T in angle brackets is the item type: List<string> holds strings, List<int> holds ints. (This <T> idea is called generics, covered in Generics.)
List<string> waiting = ["Aarav", "Priya"];
waiting.Add("Kabir");
waiting.Insert(0, "Meera");
waiting.Remove("Priya");
Console.WriteLine(waiting.Count);
Console.WriteLine(string.Join(", ", waiting));
Console.WriteLine(waiting.Contains("Kabir"));
Console.WriteLine(waiting.IndexOf("Aarav"));
waiting.Sort();
Console.WriteLine(string.Join(", ", waiting));
waiting.RemoveAt(0);
Console.WriteLine(waiting[0]);3 Meera, Aarav, Kabir True 1 Aarav, Kabir, Meera Kabir
| Member | What it does |
|---|---|
Add(x) / AddRange(items) | Adds to the end |
Insert(i, x) | Inserts at position i, shifting the rest |
Remove(x) | Removes the first matching item; returns true if found |
RemoveAt(i) | Removes the item at position i |
Count | Number of items (arrays use Length) |
Contains(x), IndexOf(x) | Searches the list |
Sort(), Reverse() | Reorders the list in place |
You can also remove every item that matches a condition, using a short lambda function (explained in Delegates, Lambdas & Events):
List<int> scores = [45, 12, 78];
scores.AddRange([33, 8, 101]);
int removed = scores.RemoveAll(s => s < 20);
Console.WriteLine($"Removed {removed} low scores");
Console.WriteLine(string.Join(", ", scores));
Console.WriteLine($"Total: {scores.Sum()}");Removed 2 low scores 45, 78, 33, 101 Total: 257
Dictionary<TKey, TValue>: look up by key
A dictionary stores key-value pairs. You look up a value by its key, like finding a PIN code from a city name. Lookups are very fast even with millions of entries, because a dictionary does not search item by item; it jumps straight to the key using its hash code.
Dictionary<string, int> pinCodes = new()
{
["Rohtak"] = 124001,
["Jaipur"] = 302001
};
pinCodes["Pune"] = 411001; // adds a new key
Console.WriteLine(pinCodes["Rohtak"]);
Console.WriteLine(pinCodes.Count);
Console.WriteLine(pinCodes.ContainsKey("Kochi"));
if (pinCodes.TryGetValue("Kochi", out int pin))
{
Console.WriteLine($"Kochi: {pin}");
}
else
{
Console.WriteLine("Kochi is not in the list yet.");
}
foreach (var (city, code) in pinCodes)
{
Console.WriteLine($"{city} -> {code}");
}124001 3 False Kochi is not in the list yet. Rohtak -> 124001 Jaipur -> 302001 Pune -> 411001
dict[key] = valueadds the key, or replaces the value if the key already exists.dict.Add(key, value)adds a key but throwsArgumentException: An item with the same key has already been added. Key: Rohtakif it exists.- Reading
dict[key]for a missing key throwsKeyNotFoundException: The given key 'Kochi' was not present in the dictionary.UseTryGetValueorContainsKeywhen a key might be missing. foreachgivesKeyValuePairitems, which you can split into(city, code)directly.
The documentation does not promise any particular order when you loop over a Dictionary. In practice you usually see insertion order, but do not rely on it. Use SortedDictionary<TKey, TValue> when you need the keys sorted.
Counting with a dictionary
Counting things is one of the most common uses of a dictionary. GetValueOrDefault returns 0 for a key that is not there yet.
string orders = "chai samosa chai jalebi chai samosa";
Dictionary<string, int> counts = new();
foreach (string item in orders.Split(' '))
{
counts[item] = counts.GetValueOrDefault(item) + 1;
}
foreach (var (item, count) in counts)
{
Console.WriteLine($"{item}: {count}");
}chai: 3 samosa: 2 jalebi: 1
HashSet<T>: unique items
A HashSet<T> stores each value at most once and checks membership very quickly. Adding a duplicate does nothing and returns false.
HashSet<string> visited = ["Pune", "Goa"];
bool added = visited.Add("Pune");
visited.Add("Kochi");
Console.WriteLine(added);
Console.WriteLine(visited.Count);
Console.WriteLine(visited.Contains("Goa"));
HashSet<string> friendVisited = ["Goa", "Jaipur"];
HashSet<string> both = new(visited);
both.IntersectWith(friendVisited);
Console.WriteLine(string.Join(", ", both));False 3 True Goa
Sets also have UnionWith, ExceptWith and IsSubsetOf. Use a HashSet instead of List.Contains when you check membership often: a list checks every item one by one, while a set finds the answer almost instantly.
Queue<T> and Stack<T>
A queue is first in, first out (FIFO), like a ticket counter line. A stack is last in, first out (LIFO), like a pile of plates or the back button in a browser.
Queue<string> counter = new();
counter.Enqueue("Rohan");
counter.Enqueue("Isha");
counter.Enqueue("Farhan");
Console.WriteLine($"Serving {counter.Dequeue()}");
Console.WriteLine($"Next: {counter.Peek()}, waiting: {counter.Count}");
Stack<string> pages = new();
pages.Push("Home");
pages.Push("Trains");
pages.Push("Booking");
Console.WriteLine($"Back from {pages.Pop()} to {pages.Peek()}");Serving Rohan Next: Isha, waiting: 2 Back from Booking to Trains
Dequeue and Pop remove and return an item; Peek only looks. Both throw InvalidOperationException when the collection is empty, so check Count first or use TryDequeue and TryPop.
Common mistakes
- Reading a missing key with
dict[key]. UseTryGetValuewhen the key may not exist. - Calling
Addfor a key that exists. Usedict[key] = valueto add or update. - Index out of range on a list.
list[list.Count]throwsArgumentOutOfRangeException. - Changing a collection inside its own
foreach. It throwsInvalidOperationException. UseRemoveAll, or collect changes first. - Relying on dictionary or set order. Sort when order matters.
Key points
List<T>is an ordered, growable list withAdd,Insert,Remove,Countand an indexer.Dictionary<TKey, TValue>maps keys to values; useTryGetValuefor safe lookups anddict[key] = valueto add or update.HashSet<T>stores unique items and checks membership fast.Queue<T>is first in, first out;Stack<T>is last in, first out.- Collection expressions such as
["a", "b"]work for lists and sets as well as arrays.
Exercise
A library in Kochi tracks borrowed books. Create a Dictionary<string, List<string>> where the key is a member name and the value is that member's list of book titles. Lend "Wings of Fire" and "Godan" to Ananya and "Malgudi Days" to Farhan. Then return "Godan" from Ananya. Print each member with the number of books and the titles.
Show solution
Dictionary<string, List<string>> loans = new();
void Lend(string member, string title)
{
if (!loans.TryGetValue(member, out List<string>? books))
{
books = [];
loans[member] = books;
}
books.Add(title);
}
Lend("Ananya", "Wings of Fire");
Lend("Ananya", "Godan");
Lend("Farhan", "Malgudi Days");
loans["Ananya"].Remove("Godan");
foreach (var (member, books) in loans)
{
Console.WriteLine($"{member} ({books.Count}): {string.Join(", ", books)}");
}Ananya (1): Wings of Fire Farhan (1): Malgudi Days
Lend is a small local function (you will study methods next). When a member has no list yet, it creates an empty one with [] and stores it. Because a List is a reference type, adding to books also changes the list inside the dictionary.
Quick Quiz
1. Which collection should you use to find a student's marks quickly from their roll number?
A dictionary looks up values by key very quickly.
2. What does dict["Pune"] = 411001; do if "Pune" is already a key?
The indexer setter adds a new key or overwrites the value of an existing key.
3. What does HashSet.Add return when the item is already in the set?
A set keeps each value once; adding a duplicate returns false and changes nothing.
4. Items are added to a Queue in the order A, B, C. What does the first Dequeue() return?
A queue is first in, first out.
Learn C# with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
