Skip to content

Intermediate · Collections, generics and LINQ · topic 22 of 43 · 35 min

Collections: List, Dictionary, HashSet, Queue and Stack

Explanation

Collections are ready-made containers that grow and shrink as needed. List<T> is an ordered, resizable list; Dictionary<TKey,TValue> finds a value by its key very fast; HashSet<T> keeps only unique items; Queue<T> is first-in-first-out and Stack<T> is last-in-first-out. Choosing the right one makes code simpler and faster.

Why not just arrays?

Arrays have a fixed size. Real data grows: new orders arrive, users join, items are removed. The System.Collections.Generic namespace (included by default) has collections that resize themselves. The <T> part means "of some type T": List<string> is a list of strings, List<decimal> a list of decimals.

List<T>: the everyday collection

csharp
var cart = new List<string> { "Rice", "Dal" };
cart.Add("Oil");                 // Rice, Dal, Oil
cart.Insert(0, "Salt");          // Salt, Rice, Dal, Oil
cart.Remove("Dal");              // removes the first "Dal"; returns true
cart.RemoveAt(0);                // removes "Salt"
Console.WriteLine(cart.Count);   // 2
Console.WriteLine(cart[1]);      // Oil
Console.WriteLine(cart.Contains("Rice"));   // True
Console.WriteLine(string.Join(", ", cart)); // Rice, Oil
Count (not Length) gives the number of items. Indexes start at 0, like arrays.

Dictionary<TKey,TValue>: look up by key

A dictionary stores pairs: a key and its value. Give the key, get the value, almost instantly, even with a million entries. Keys must be unique.

csharp
var stock = new Dictionary<string, int>
{
    ["Pen"] = 120,
    ["Notebook"] = 45
};
stock["Eraser"] = 60;            // add a new key
stock["Pen"] = 100;              // replace an existing value
stock.Add("Pencil", 80);         // Add throws if the key already exists

if (stock.TryGetValue("Stapler", out int qty))
    Console.WriteLine(qty);
else
    Console.WriteLine("Stapler not stocked");

foreach (var (item, count) in stock)
    Console.WriteLine($"{item}: {count}");
TryGetValue is the safe way to read. stock["Stapler"] on a missing key would throw KeyNotFoundException.
Think of it like this: A List is like a queue of people where you find someone by their position. A Dictionary is like a phone's contact list: you type the name (the key) and get the number (the value) straight away, without scrolling.

HashSet<T>: unique items

csharp
var visitors = new HashSet<string>();
Console.WriteLine(visitors.Add("asha@mail.com"));   // True
Console.WriteLine(visitors.Add("ravi@mail.com"));   // True
Console.WriteLine(visitors.Add("asha@mail.com"));   // False: already there
Console.WriteLine(visitors.Count);                   // 2
Add returns false for duplicates. Contains is very fast. UnionWith and IntersectWith do set maths.

Queue<T> and Stack<T>

csharp
var tokens = new Queue<int>();          // first in, first out (FIFO)
tokens.Enqueue(101);
tokens.Enqueue(102);
tokens.Enqueue(103);
Console.WriteLine(tokens.Dequeue());   // 101: served first
Console.WriteLine(tokens.Peek());      // 102: next, not removed

var undo = new Stack<string>();         // last in, first out (LIFO)
undo.Push("typed A");
undo.Push("typed B");
Console.WriteLine(undo.Pop());         // typed B: the latest action is undone first
A queue is like a bank token line. A stack is like a pile of plates: you take the top one first.
Careful: Dequeue or Pop on an empty collection throws InvalidOperationException. Check Count first or use TryDequeue / TryPop.

Which collection should I use?

NeedUseWhy
An ordered list you add to and loop overList<T>Simple, indexed, resizable
Find a value by an id, code or nameDictionary<TKey,TValue>Very fast lookup by key
Only unique values; fast "have I seen this?"HashSet<T>Ignores duplicates, fast Contains
Process in arrival orderQueue<T>First in, first out
Undo, back button, nested bracketsStack<T>Last in, first out
Fixed number of items that never changesArraySmallest and simplest

Speed in plain words

list.Contains(x) checks items one by one, so on a list of one lakh items it may check one lakh items. A HashSet or Dictionary jumps almost straight to the right place using the item's hash code (a number computed from the value), so the time hardly grows with size. If you search inside a loop, a Dictionary or HashSet can turn a slow program into a fast one.

Common mistakes and fixes

  • Changing a list inside foreach over it: InvalidOperationException. Loop backwards with for, or use list.RemoveAll(x => ...).
  • Using dict[key] for a key that may be missing: KeyNotFoundException. Use TryGetValue or ContainsKey.
  • Relying on dictionary order: the order of items in a Dictionary is not guaranteed. Sort the keys if order matters.
  • Using List.Contains in a big loop: switch to a HashSet.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Collections: List, Dictionary, HashSet, Queue and Stack · 1/7

Collections

In this lesson you will use collections that grow and shrink as needed.

Prefer a video by a teacher? Find free videos on “C# List Dictionary HashSet Queue Stack collections tutorial” on YouTube ↗

In real life: Counting votes in a college election

A college in Bhubaneswar runs a class-representative election on a simple app. Each vote is a candidate name. A dictionary counts the votes, and a HashSet makes sure each roll number votes only once.

csharp
var ballots = new List<(int RollNo, string Candidate)>
{
    (101, "Priya"), (102, "Arjun"), (103, "Priya"), (101, "Arjun"), (104, "Priya")
};

var voted = new HashSet<int>();
var count = new Dictionary<string, int>();

foreach (var (roll, candidate) in ballots)
{
    if (!voted.Add(roll))
    {
        Console.WriteLine($"Roll {roll} already voted, ignored");
        continue;
    }
    count[candidate] = count.TryGetValue(candidate, out int c) ? c + 1 : 1;
}

foreach (var (name, votes) in count)
    Console.WriteLine($"{name}: {votes}");
Shows Roll 101 already voted, ignored, then Priya: 3 and Arjun: 1.

Practice (0/4 done)

Task 1 · Question

var l = new List<int> { 5, 6, 7 }; l.RemoveAt(0); What is l[0]?

Task 2 · Question

var q = new Queue<string>(); q.Enqueue("A"); q.Enqueue("B"); q.Enqueue("C"); q.Dequeue(); Console.WriteLine(q.Peek());

Task 3 · Question

Which collection is best to check quickly if a PAN number was already registered among 10 lakh records?

Task 4 · Practice

Given a sentence, count how many times each word appears (ignore case) and print the words with their counts. Use a Dictionary<string, int>. Try it with "the cat and the dog and THE bird".

Quick quiz

Question 1

1.What happens with dict["missing"] when the key does not exist?

Question 2

2.Which property gives the number of items in a List<T>?

Question 3

3.Stack: Push(1), Push(2), Push(3), Pop(). What was returned?

Question 4

4.What does HashSet<T>.Add return for a value already in the set?

Question 5

5.dict["Pen"] = 50 when "Pen" already exists...

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What happens with dict["missing"] when the key does not exist?

Answer: KeyNotFoundException

Reading a missing key with the indexer throws. Use TryGetValue.

Which property gives the number of items in a List<T>?

Answer: Count

Collections use Count; arrays use Length.

Stack: Push(1), Push(2), Push(3), Pop(). What was returned?

Answer: 3

Last in, first out: 3.

What does HashSet<T>.Add return for a value already in the set?

Answer: false

false means nothing was added.

dict["Pen"] = 50 when "Pen" already exists...

Answer: Replaces the old value

The indexer setter adds or replaces. Add() would throw.

What does HashSet::insert return when the value is already present?Related

Answer: false

insert returns false if the set already had the value.

From HashMap and HashSet

Which is a generic type in .NET?Related

Answer: Dictionary<TKey,TValue>

Dictionary has two type parameters.

From Generics: reusable, type-safe code

std::priority_queue<int> pq; after pushing 4, 9 and 2, what is pq.top()?Related

Answer: 9

By default priority_queue is a max-heap.

From set, stack, queue, deque and priority_queue

Which order does a stack follow?Related

Answer: Last In, First Out

A stack is LIFO: the most recently added item is removed first.

From Stacks and queues

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Collections, generics and LINQ” before its exam: Generics: reusable, type-safe code, LINQ basics: query data in a few lines, LINQ in depth: grouping, joins, deferred execution, IQueryable.
  2. Module 6 exam: Collections, generics and LINQ · 14 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Exceptions, delegates, events and async. 43 topics left in the course before the final exam.

← Previous: Records and pattern matching