Collections
By the end of this lesson you'll be able to store many values in one place and pick the right tool for the job — a growable List T for ordered items, a Dictionary K,V for instant lookups by name, a HashSet T for unique values, and Stack / Queue for ordered processing.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
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.
Think of collections as different containers in your kitchen. A List is a numbered shelf — items sit in order and you can grab one by its position. A Dictionary is a labelled spice rack — you find "cumin" instantly by name, never by counting jars. A HashSet is a bowl of unique fridge magnets — adding a duplicate changes nothing. A Stack is a pile of plates — you always take from the top (last on, first off). A Queue is the checkout line — first person in is the first served. Picking the right container is half the job; the rest is just Add , Remove , and looking things up.
Rule of thumb: reach for List T by default, switch to Dictionary K,V the moment you find yourself searching by a name or ID, and use HashSet T when duplicates would be a bug.
1. Arrays vs List T
An array ( string[] ) has a fixed length — you set its size once and it can never grow or shrink. That's perfect when the count is known and stable. But most of the time you don't know how many items you'll have, so you reach for a List . A List T is a resizable array: it starts empty and grows automatically every time you .Add(...) . The T is the type it holds — List string holds strings, List int holds whole numbers. Read this worked example first, then you'll build one yourself.
2. Working with List T
A List T gives you a handful of methods you'll use constantly: Add appends an item, Remove deletes the first matching value, Contains answers a yes/no membership question, Count tells you how many items there are, Sort reorders them, and list[i] reads or writes the item at position i (remember, the first item is index 0 ). The cleanest way to visit every item is a foreach . Read the worked example, then finish the one below it.
Your turn. The shopping-list program below is almost complete — fill in the three ___ blanks using the hints in the comments, then run it and check the output.
3. Dictionary TKey, TValue
A Dictionary stores key → value pairs, and looking a value up by its key is almost instant (on average O(1) — it doesn't slow down as the dictionary grows). Use one whenever you find data by a unique identifier: a username, a product code, a setting name. Add or overwrite with dict[key] = value , and read with dict[key] — but only if you're certain the key exists. The safe way to read is TryGetValue , which returns false instead of crashing when the key is missing.
Now you try. The stock-tracker below maps a product name to how many you have in stock. Fill in the three ___ blanks, then run it.
4. HashSet T — Unique Values
A HashSet T holds only unique values — try to add a duplicate and it's quietly ignored, so the set never contains the same item twice. That makes it ideal for tags, permission lists, and stripping duplicates out of data. It also does proper set maths: UnionWith (everything in either set), IntersectWith (only what's in both), and ExceptWith (remove the overlap). Membership tests with Contains are very fast — much faster than searching a List for big collections.
5. Stack T and Queue T
These two control the order in which you take things out. A Stack is LIFO — Last In, First Out — like a stack of plates or a browser's "back" history: Push adds to the top, Pop removes the top, Peek looks without removing. A Queue is FIFO — First In, First Out — like a checkout line or a print queue: Enqueue joins the back, Dequeue serves the front. Whenever order-of-processing matters, one of these two is exactly what you want.
This is the single most common collection bug. Adding to or removing from a list inside a foreach over that same list throws InvalidOperationException: Collection was modified . The loop relies on the collection staying still; you pulled the rug out from under it.
RemoveAll (with a small condition called a lambda) is usually the cleanest fix when you just want to drop everything that matches a rule.
Here's a small but genuinely useful program that combines this lesson's ideas — an array of words from Split , and a Dictionary string,int that counts how many times each word appears. The ContainsKey check is the heart of it: seen the word before? add one; first time? start at one. You understand every line now.
This "count occurrences" pattern is everywhere — tallying votes, log levels, inventory, survey answers. Learn it once and you'll reuse it constantly.
Q: When should I use an array instead of a List T ?
Use an array only when the number of items is fixed and known up front (e.g. the 7 days of the week, or a chess board's 64 squares). For everything that grows, shrinks, or has an unknown size, use a List T — it does everything an array does plus Add / Remove .
Q: How is a Dictionary different from a List ?
A List finds items by their numeric position ( list[2] ). A Dictionary finds them by a meaningful key ( ages["Alice"] ) and that lookup is near-instant regardless of size. If you ever loop a list just to find the item with a matching name or ID, a dictionary will be faster and clearer.
Q: Why did adding a duplicate to my HashSet do nothing?
That's the whole point of a set — it holds each value only once. Add returns false when the value is already present and the count doesn't change. If you need to keep duplicates, use a List T instead.
Q: What's the practical difference between a Stack and a Queue?
A Stack hands back the most recently added item (LIFO) — great for undo and backtracking. A Queue hands back the oldest item (FIFO) — great for processing things fairly in arrival order, like jobs or messages.
No blanks this time — just a brief and an outline to keep you on track. Build a Dictionary string,int that counts how many times each word appears in a sentence, then print each word with its count. Run it and check your output against the expected lines in the comments.
Practice quiz
What is the key difference between an array and a List<T>?
- An array can grow; a List<T> is fixed-size
- They are identical
- An array is fixed-size; a List<T> is a resizable array that grows as you Add items
- A List<T> cannot be indexed
Answer: An array is fixed-size; a List<T> is a resizable array that grows as you Add items. An array has a fixed length set once. A List<T> is a resizable array that starts empty and grows automatically when you Add.
Which List<T> method appends an item to the end?
- Add()
- Push()
- Enqueue()
- Insert()
Answer: Add(). List<T>.Add(item) appends an item to the end of the list.
What is the index of the first item in a List<T>?
- 1
- -1
- It varies
- 0
Answer: 0. Indexes start at 0, so the first item is list[0] and the last is list[list.Count - 1].
What does a Dictionary<K,V> map?
- Positions to values
- Keys to values, with near-instant lookup by key (on average O(1))
- Values to values only
- Indexes to keys
Answer: Keys to values, with near-instant lookup by key (on average O(1)). A Dictionary maps each unique key to a value, and looking a value up by its key is on average O(1) regardless of size.
What is the safe way to read a Dictionary value that might be missing?
- TryGetValue(key, out value), which returns false instead of throwing
Answer: TryGetValue(key, out value), which returns false instead of throwing. dict[key] throws KeyNotFoundException for a missing key. TryGetValue returns false (and the value via out) instead of crashing.
What happens when you Add a duplicate value to a HashSet<T>?
- It throws an exception
- It stores the duplicate anyway
- It is silently ignored — the set keeps each value only once
- It clears the set
Answer: It is silently ignored — the set keeps each value only once. A HashSet holds only unique values; adding a duplicate is silently ignored (Add returns false) and the count doesn't change.
A Stack<T> processes items in which order?
- First-in, first-out (FIFO)
- Last-in, first-out (LIFO)
- Sorted order
- Random order
Answer: Last-in, first-out (LIFO). A Stack is LIFO — Push adds to the top, Pop removes the top, like a pile of plates.
Which pair of methods does a Queue<T> (FIFO) use to add and remove items?
- Push / Pop
- Add / Remove
- Insert / Delete
- Enqueue / Dequeue
Answer: Enqueue / Dequeue. A Queue is FIFO: Enqueue joins the back of the line and Dequeue serves the front.
What happens if you add to or remove from a collection while iterating it with foreach?
- Nothing — it works fine
- It throws InvalidOperationException: Collection was modified
- The loop silently skips items
- It doubles the collection
Answer: It throws InvalidOperationException: Collection was modified. Modifying a collection during a foreach over it throws 'Collection was modified'. Loop a copy (.ToList()) or use RemoveAll(...).
Which namespace must you import to use List, Dictionary, HashSet, Stack and Queue?
- System.Linq
- System.Text
- System.Collections.Generic
- System.IO
Answer: System.Collections.Generic. All of these generic collections live in System.Collections.Generic, so you need 'using System.Collections.Generic;'.