Data Structures in C#

Learn Data Structures in C# with practical examples, explanations, best practices, and real-world coding techniques.

Data Structures in C#

C# provides a rich collection of built-in data structures through the System.Collections.Generic namespace. These collections make it easier to store, access, search, update, and organize data without implementing every data structure from scratch.

Understanding these collections is important for writing efficient C# applications and for solving programming and data structure problems. The right collection depends on how the application needs to access and modify its data.

Common Data Structures Available in C#

C# Collection Data Structure Concept Typical Use
Array Array Fixed-size indexed data
List<T> Dynamic array Resizable sequential collections
LinkedList<T> Doubly linked list Node-based sequences
Stack<T> Stack LIFO processing
Queue<T> Queue FIFO processing
Dictionary<TKey, TValue> Hash table Key-value lookup
HashSet<T> Hash set Unique values and membership checks

Arrays in C#

A C# array stores a fixed number of elements of the same type. Elements can be accessed directly using their zero-based index.

int[] numbers = { 10, 20, 30, 40, 50 };

Console.WriteLine(numbers[2]);
// 30

numbers[2] = 35;

Console.WriteLine(numbers[2]);
// 35

Array access by index is typically O(1). However, inserting or deleting elements requires shifting values or creating another array because the array has a fixed length.

List<T> as a Dynamic Array

List<T> is one of the most frequently used generic collections in C#. It provides a resizable array-like structure while retaining fast indexed access.

using System;
using System.Collections.Generic;

var numbers = new List<int>();

numbers.Add(10);
numbers.Add(20);
numbers.Add(30);

Console.WriteLine(numbers[1]);
// 20

numbers.Remove(20);

foreach (int number in numbers)
{
    Console.WriteLine(number);
}

A List<T> automatically grows when its internal capacity is reached. This makes it convenient when the number of elements is not known in advance.

List Capacity and Count

A useful detail when working with List<T> is the difference between Count and Capacity. Count represents the number of elements currently stored, while Capacity represents the allocated storage available internally.

var values = new List<int>();

values.Add(10);
values.Add(20);
values.Add(30);

Console.WriteLine(values.Count);
Console.WriteLine(values.Capacity);

Stack<T> in C#

Stack<T> implements a Last-In, First-Out (LIFO) structure. The most recently added element is the first element removed.

var stack = new Stack<string>();

stack.Push("Page 1");
stack.Push("Page 2");
stack.Push("Page 3");

Console.WriteLine(stack.Pop());
// Page 3

Console.WriteLine(stack.Peek());
// Page 2

Common stack operations include Push() to add an element, Pop() to remove the top element, and Peek() to inspect the top element without removing it.

Queue<T> in C#

Queue<T> implements a First-In, First-Out (FIFO) structure. The element that enters the queue first is normally the first element removed.

var queue = new Queue<string>();

queue.Enqueue("Customer A");
queue.Enqueue("Customer B");
queue.Enqueue("Customer C");

Console.WriteLine(queue.Dequeue());
// Customer A

Console.WriteLine(queue.Peek());
// Customer B

Enqueue() adds an element, Dequeue() removes the front element, and Peek() reads the front element without removing it.

Dictionary<TKey, TValue> in C#

Dictionary<TKey, TValue> stores data as key-value pairs. It is backed by a hash table and is useful when values need to be retrieved using unique keys.

var users = new Dictionary<int, string>
{
    { 101, "Asha" },
    { 102, "Rahul" },
    { 103, "Priya" }
};

Console.WriteLine(users[102]);
// Rahul

users[102] = "Rahul Kumar";

Console.WriteLine(users[102]);
// Rahul Kumar

Dictionary lookup, insertion, and removal are typically O(1) on average, although the actual performance depends on hashing and collision behavior.

Safe Dictionary Lookup

Accessing a missing key directly through the indexer throws a KeyNotFoundException. When the key may not exist, TryGetValue() provides a safer lookup pattern.

var scores = new Dictionary<string, int>
{
    { "Asha", 95 },
    { "Rahul", 88 }
};

if (scores.TryGetValue("Priya", out int score))
{
    Console.WriteLine(score);
}
else
{
    Console.WriteLine("User not found");
}

HashSet<T> in C#

HashSet<T> stores unique values and is optimized for membership checks. Adding a duplicate value does not create another element.

var tags = new HashSet<string>();

tags.Add("C#");
tags.Add(".NET");
tags.Add("Data Structures");
tags.Add("C#");

Console.WriteLine(tags.Count);
// 3

Console.WriteLine(tags.Contains(".NET"));
// True

A hash set is useful when uniqueness matters more than maintaining elements at numeric indexes.

LinkedList<T> in C#

LinkedList<T> provides a doubly linked list. Each node contains a value and references that connect it to neighboring nodes.

var list = new LinkedList<string>();

list.AddLast("HTML");
list.AddLast("CSS");
list.AddLast("JavaScript");

var cssNode = list.Find("CSS");

if (cssNode != null)
{
    list.AddAfter(cssNode, "TypeScript");
}

foreach (string item in list)
{
    Console.WriteLine(item);
}

A linked list is useful when nodes need to be inserted or removed without shifting all subsequent elements. However, accessing an arbitrary position requires traversal and is therefore not constant time.

Choosing the Right C# Data Structure

The best collection depends on the operations your application performs most frequently.

Requirement Suitable C# Collection
Fixed-size indexed data Array
Resizable list with index access List<T>
LIFO processing Stack<T>
FIFO processing Queue<T>
Key-value lookup Dictionary<TKey, TValue>
Unique values HashSet<T>
Node-based sequence LinkedList<T>

C# Data Structure Complexity

Understanding the typical complexity of collection operations helps when selecting a data structure for an application.

Collection Access Search / Lookup Typical Add
Array O(1) O(n) Fixed size
List<T> O(1) O(n) O(1) amortized at end
LinkedList<T> O(n) O(n) O(1) with a known node
Stack<T> Top only O(n) O(1) amortized
Queue<T> Front only O(n) O(1) amortized
Dictionary<TKey, TValue> By key O(1) average O(1) average
HashSet<T> Not index-based O(1) average O(1) average

Generic Collections in C#

Generic collections such as List<T>, Stack<T>, and Dictionary<TKey, TValue> allow the compiler to enforce the types of values stored in the collection.

var numbers = new List<int>();

numbers.Add(10);
numbers.Add(20);

// numbers.Add("30"); // Compile-time type error

Generic collections are generally preferred over older non-generic collection types because they provide stronger type safety and avoid many unnecessary conversions.

Common Mistakes When Choosing C# Collections

  • Using an array when the collection needs to grow dynamically.
  • Using a list when the primary requirement is key-based lookup.
  • Using a dictionary when duplicate keys are expected.
  • Using a hash set when the application needs indexed access.
  • Choosing a linked list without considering the cost of traversal.
  • Ignoring the difference between average and worst-case complexity.
  • Using non-generic collections when a generic collection is appropriate.

Practical Example: Managing Product Data

Different data structures can work together in the same application. For example, a product list can use List<T>, product lookup can use a Dictionary<TKey, TValue>, and unique categories can use a HashSet<T>.

record Product(int Id, string Name, string Category);

var products = new List<Product>
{
    new Product(1, "Laptop", "Electronics"),
    new Product(2, "Keyboard", "Electronics"),
    new Product(3, "Notebook", "Stationery")
};

var productById = products.ToDictionary(product => product.Id);

var categories = products
    .Select(product => product.Category)
    .ToHashSet();

Console.WriteLine(productById[2].Name);
// Keyboard

Console.WriteLine(categories.Count);
// 2

This example demonstrates an important principle: an application does not have to use one data structure for everything. Different collections can be selected for different access patterns.

Key Takeaways

  • C# provides several generic collections for common data structure requirements.
  • Array is useful for fixed-size indexed data.
  • List<T> provides a resizable array-like collection.
  • Stack<T> follows LIFO ordering.
  • Queue<T> follows FIFO ordering.
  • Dictionary<TKey, TValue> provides key-value storage and fast average lookup.
  • HashSet<T> is designed for unique values and membership checks.
  • LinkedList<T> provides a doubly linked node-based structure.
  • The correct collection depends on the operations your application performs most often.

Code Examples

using System;
using System.Collections.Generic;

var numbers = new List<int> { 10, 20, 30 };

var stack = new Stack<int>();
stack.Push(10);
stack.Push(20);

var queue = new Queue<int>();
queue.Enqueue(100);
queue.Enqueue(200);

Console.WriteLine(numbers[1]);
// 20

Console.WriteLine(stack.Pop());
// 20

Console.WriteLine(queue.Dequeue());
// 100
    
using System;
using System.Collections.Generic;

var users = new Dictionary<int, string>
{
    { 101, "Asha" },
    { 102, "Rahul" }
};

var uniqueTags = new HashSet<string>
{
    "C#",
    ".NET",
    "C#"
};

Console.WriteLine(users[101]);
// Asha

Console.WriteLine(uniqueTags.Count);
// 2

Console.WriteLine(uniqueTags.Contains(".NET"));
// True
    
using System;
using System.Collections.Generic;

var products = new List<string>
{
    "Laptop",
    "Keyboard",
    "Mouse"
};

var productLookup = new Dictionary<int, string>
{
    { 1, "Laptop" },
    { 2, "Keyboard" },
    { 3, "Mouse" }
};

var categories = new HashSet<string>
{
    "Electronics",
    "Electronics",
    "Accessories"
};

Console.WriteLine(products[0]);
// Laptop

Console.WriteLine(productLookup[2]);
// Keyboard

Console.WriteLine(categories.Count);
// 2
    

Pro Tip

Do not choose a C# collection only because it is familiar. Start with the operations your application needs most frequently—indexed access, key lookup, uniqueness, FIFO/LIFO processing, or node-based insertion—and then choose the collection whose characteristics match those operations.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.