Loading...

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
  • SortedList may shift items on insert/remove to maintain order. If you need frequent inserts/removals, prefer SortedDictionary.
  • If you need index-based access to ranges, SortedList is 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.