Collections in C#: List, Dictionary, Queue, Stack
Learn C# collections like List, Dictionary, Queue, and Stack to store and manage data efficiently with practical examples.
In C#, collections are used to store multiple values dynamically and process them efficiently.
Arrays have fixed size, but collections can grow and shrink as needed. This article covers
List<T>, Dictionary<TKey,TValue> (and KeyValuePair<,>),
SortedList<TKey,TValue>, Queue<T>, Stack<T>,
HashSet<T>, and LinkedList<T> with examples.
List<T>
List<T> is a dynamically resizable collection that holds items of the same type in order.
It provides rich APIs for adding/removing, searching, and sorting.
using System;
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int> { 5, 1, 9 };
numbers.Add(3); // add at the end
numbers.Insert(1, 7); // insert at a given index
numbers.Remove(9); // remove by value (first match)
numbers.RemoveAll(x => x % 2 == 1); // remove all odd numbers
numbers.AddRange(new[] { 10, 2, 8 });
numbers.Sort(); // sort ascending
numbers.Reverse(); // reverse order
Console.WriteLine(string.Join(", ", numbers));
Console.WriteLine(numbers.Contains(8)); // true
// LINQ filtering
var evens = numbers.Where(n => n % 2 == 0).ToList();
Console.WriteLine(string.Join(", ", evens));
Dictionary<TKey,TValue> and KeyValuePair<TKey,TValue>
Dictionary stores key–value pairs. Keys are unique.
It is hash-based for high-performance lookups.
using System;
using System.Collections.Generic;
var stockPrices = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
["Apple"] = 5.75m,
["Pear"] = 7.25m
};
// Add or update
stockPrices["Banana"] = 12m; // adds if not exists, updates if exists
// Safe read
if (stockPrices.TryGetValue("APPLE", out var price))
Console.WriteLine($"Apple: {price} USD");
// Key existence
if (!stockPrices.ContainsKey("Cherry"))
stockPrices.Add("Cherry", 18.5m);
// Iteration with KeyValuePair
foreach (KeyValuePair<string, decimal> kv in stockPrices)
Console.WriteLine($"{kv.Key} → {kv.Value} USD");
// Update value
stockPrices["Apple"] = stockPrices["Apple"] * 1.10m; // +10% price
Tip: KeyValuePair<TKey,TValue> provides .Key and .Value
when iterating a dictionary. For updates, directly use dict[key].
SortedList<TKey,TValue>
SortedList keeps keys in ascending order and provides index-based access via Keys and Values.
It combines dictionary-like and array-like behaviors.
using System;
using System.Collections.Generic;
var priceList = new SortedList<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
["Pear"] = 7.25m,
["Apple"] = 5.75m,
["Banana"] = 12m,
["Cherry"] = 18.5m
};
// Sorted keys:
Console.WriteLine(string.Join(", ", priceList.Keys)); // Apple, Banana, Cherry, Pear
// Index-based access:
for (int i = 0; i < priceList.Count; i++)
Console.WriteLine($"#{i} {priceList.Keys[i]} → {priceList.Values[i]}");
// Custom comparer (reverse order):
var reversed = new SortedList<string, decimal>(Comparer<string>.Create((a,b) => StringComparer.OrdinalIgnoreCase.Compare(b,a)));
reversed["Apple"] = 5.75m;
reversed["Pear"] = 7.25m;
Console.WriteLine(string.Join(", ", reversed.Keys)); // reverse order
SortedListmay shift items on insert/remove to maintain order. If you need frequent inserts/removals, preferSortedDictionary.- If you need index-based access to ranges,
SortedListis advantageous.
Queue<T> (FIFO)
Queue works with first in, first out logic; useful for workflows and task queues.
using System;
using System.Collections.Generic;
var orders = new Queue<string>();
orders.Enqueue("Order#1001");
orders.Enqueue("Order#1002");
orders.Enqueue("Order#1003");
Console.WriteLine(orders.Peek()); // next without removing: Order#1001
Console.WriteLine(orders.Dequeue()); // removes: Order#1001
Console.WriteLine(orders.Count); // 2
Stack<T> (LIFO)
Stack works with last in, first out logic; used for undo, navigation history, etc.
using System;
using System.Collections.Generic;
var steps = new Stack<string>();
steps.Push("Step-1");
steps.Push("Step-2");
steps.Push("Step-3");
Console.WriteLine(steps.Peek()); // Step-3
Console.WriteLine(steps.Pop()); // Step-3 (removed)
Console.WriteLine(steps.Pop()); // Step-2
HashSet<T>
HashSet stores a collection of unique elements. Duplicates are ignored.
It is ideal for set operations (union, intersection, difference).
using System;
using System.Collections.Generic;
var a = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "apple", "pear", "banana" };
var b = new HashSet<string> { "Banana", "cherry" };
var union = new HashSet<string>(a);
union.UnionWith(b); // A ∪ B
var intersection = new HashSet<string>(a);
intersection.IntersectWith(b); // A ∩ B
var difference = new HashSet<string>(a);
difference.ExceptWith(b); // A \ B
Console.WriteLine(string.Join(", ", union)); // apple, pear, banana, cherry
Console.WriteLine(string.Join(", ", intersection)); // banana
Console.WriteLine(string.Join(", ", difference)); // apple, pear
Console.WriteLine(a.Add("Apple")); // false (already exists, case-insensitive)
Tip: Membership tests (Contains) and adds are typically O(1) amortized in HashSet; great for filtering duplicates in large datasets.
LinkedList<T>
LinkedList is a doubly linked list. Insertions/removals at known nodes are O(1).
There is no index-based random access; traversal is required.
using System;
using System.Collections.Generic;
var list = new LinkedList<string>();
var node1 = list.AddFirst("Start");
var node2 = list.AddLast("End");
list.AddAfter(node1, "Middle-1"); // Start <-> Middle-1 <-> End
list.AddBefore(node2, "Middle-2"); // Start <-> Middle-1 <-> Middle-2 <-> End
// Remove a node
var middle1 = node1.Next;
list.Remove(middle1);
// Traverse
for (var n = list.First; n != null; n = n.Next)
Console.WriteLine(n.Value); // Start, Middle-2, End
- Fast for head/tail operations and node-relative insert/remove.
- Not suitable for random index-based access; use
List<T>instead.
Sample Application: Order Processing Pipeline
In the following scenario, different collections are combined: Queue for pending orders, Dictionary for stock and prices, HashSet for unique customers, Stack for undo, SortedList for price list, and LinkedList for “recently viewed products.”
using System;
using System.Collections.Generic;
var pending = new Queue<string>(new[] { "ORD-1001", "ORD-1002" });
var stock = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["Apple"] = 100, ["Pear"] = 50, ["Banana"] = 30
};
var prices = new SortedList<string, decimal> { ["Pear"] = 7.25m, ["Apple"] = 5.75m, ["Banana"] = 12m };
var customers = new HashSet<string>(); // unique customer IDs
var undo = new Stack<Action>(); // undo stack
var recent = new LinkedList<string>(); // recently viewed products
void AddToCart(string product, int qty)
{
if (stock.TryGetValue(product, out var s) && s >= qty)
{
stock[product] = s - qty;
undo.Push(() => stock[product] += qty); // rollback action
recent.AddFirst(product);
Console.WriteLine($"{qty} x {product} added. Remaining: {stock[product]}");
}
else
{
Console.WriteLine($"Insufficient stock: {product}");
}
}
void ProcessOrder(string orderNo, string customerId)
{
Console.WriteLine($"Processing: {orderNo}");
customers.Add(customerId);
AddToCart("Apple", 3);
AddToCart("Pear", 2);
}
while (pending.Count > 0)
{
var order = pending.Dequeue();
ProcessOrder(order, "CUST-001");
}
Console.WriteLine($"Unique customers: {customers.Count}");
Console.WriteLine($"Price list (sorted): {string.Join(", ", prices.Keys)}");
// Rollback if needed:
if (undo.Count > 0)
{
var rollback = undo.Pop();
rollback();
}
Console.WriteLine("Recently viewed (top 3):");
int k = 0;
for (var n = recent.First; n != null && k < 3; n = n.Next, k++)
Console.WriteLine($"- {n.Value}");
TL;DR
- List<T>: Dynamic, index-based list. Great for random access.
- Dictionary<TKey,TValue>: Key–value pairs, fast lookups. Iterate with KeyValuePair.
- SortedList<TKey,TValue>: Sorted keys + index access. Good for range queries.
- Queue<T> (FIFO): Workflows/queues.
- Stack<T> (LIFO): Undo, history.
- HashSet<T>: Unique elements; union/intersection/difference operations.
- LinkedList<T>: Node-based list; fast node-relative insert/remove.
Guide: Random access → List, fast key lookup → Dictionary, sorted keys with index → SortedList, uniqueness → HashSet, FIFO → Queue, LIFO → Stack, fast head/tail operations → LinkedList.
Related Articles
Arrays in C#
Learn arrays in C#, including declaration, indexing, looping through elements, and common array operations with examples.
C# Loops (for, foreach, while, do-while)
Learn how to use for, foreach, while, and do-while loops in C#. Discover practical examples for handling repeated operations in C# applications.
Generics in C# (List<T>, Dictionary<TKey,TValue>)
Learn generics in C#, including List<T> and Dictionary<TKey,TValue>, to write type-safe and reusable code with examples.
Lambda Expressions in C#
Learn lambda expressions in C#, including concise syntax, Func and Action delegates, and practical LINQ usage examples.