Lists, Dictionaries & Sets

C# · Lesson 13 of 31 6 min read Updated

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.)

C#
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]);
Output
3
Meera, Aarav, Kabir
True
1
Aarav, Kabir, Meera
Kabir
MemberWhat 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
CountNumber 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):

C#
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()}");
Output
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.

C#
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}");
}
Output
124001
3
False
Kochi is not in the list yet.
Rohtak -> 124001
Jaipur -> 302001
Pune -> 411001
  • dict[key] = value adds the key, or replaces the value if the key already exists.
  • dict.Add(key, value) adds a key but throws ArgumentException: An item with the same key has already been added. Key: Rohtak if it exists.
  • Reading dict[key] for a missing key throws KeyNotFoundException: The given key 'Kochi' was not present in the dictionary. Use TryGetValue or ContainsKey when a key might be missing.
  • foreach gives KeyValuePair items, 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.

C#
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}");
}
Output
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.

C#
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));
Output
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.

C#
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()}");
Output
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]. Use TryGetValue when the key may not exist.
  • Calling Add for a key that exists. Use dict[key] = value to add or update.
  • Index out of range on a list. list[list.Count] throws ArgumentOutOfRangeException.
  • Changing a collection inside its own foreach. It throws InvalidOperationException. Use RemoveAll, or collect changes first.
  • Relying on dictionary or set order. Sort when order matters.

Key points

  • List<T> is an ordered, growable list with Add, Insert, Remove, Count and an indexer.
  • Dictionary<TKey, TValue> maps keys to values; use TryGetValue for safe lookups and dict[key] = value to 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
C#
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)}");
}
Output
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?

2. What does dict["Pune"] = 411001; do if "Pune" is already a key?

3. What does HashSet.Add return when the item is already in the set?

4. Items are added to a Queue in the order A, B, C. What does the first Dequeue() return?

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