Working with Lists
Vaibhav • September 10, 2025
In the previous section, we introduced generic collections and saw how they improve on arrays
by
being type-safe and dynamic. Among all generic collections, the most commonly used is the
List<T>
. Think of it as a more flexible array - it can grow and shrink
automatically, provides powerful built-in methods, and is optimized for most everyday scenarios.
What is a List?
A List<T>
is a dynamic array. It stores elements in
contiguous memory like a normal array, but behind the scenes it manages resizing automatically when you add more
items than it can hold. This makes it perfect for cases where the number of elements is not known in advance.
List<T>
belongs to the
System.Collections.Generic
namespace. It is generic, which means you must
specify the type of elements it will store (e.g., List<int>
,
List<string>
).
Declaring and Initializing a List
Creating a list is simple. Here are a few common ways:
// Declaring an empty list
List<string> names = new List<string>();
// Declaring with initial capacity
List<int> numbers = new List<int>(10);
// Declaring with initializer syntax
List<char> vowels = new List<char> { 'a', 'e', 'i', 'o', 'u' };
Unlike arrays, you don’t need to know the exact size in advance. If the list runs out of space, it automatically resizes itself.
Adding and Removing Elements
Lists provide several methods to manage elements efficiently. Let’s look at some of the most common ones:
List<string> fruits = new List<string>();
// Adding elements
fruits.Add("Apple");
fruits.Add("Banana");
fruits.Add("Cherry");
// Inserting at a specific index
fruits.Insert(1, "Mango"); // Apple, Mango, Banana, Cherry
// Removing by value
fruits.Remove("Banana"); // Apple, Mango, Cherry
// Removing by index
fruits.RemoveAt(0); // Mango, Cherry
Each of these operations is carefully optimized under the hood. For example,
Remove
will find the first matching element and shift the remaining elements to
fill the gap.
Accessing Elements
Just like arrays, lists use zero-based indexing. You can retrieve elements using the indexer syntax:
Console.WriteLine(fruits[0]); // Output: Mango
You can also loop through the list using for
or
foreach
.
// Using for loop
for (int i = 0; i < fruits.Count; i++)
{
Console.WriteLine(fruits[i]);
}
// Using foreach
foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}
Lists expose a Count
property (number of elements) -
this is different from arrays, which use Length
.
Capacity vs Count
One of the most interesting aspects of lists is how they manage memory. A list maintains two important values:
- Count: The number of actual elements in the list.
- Capacity: The number of elements the list can hold before resizing.
List<int> numbers = new List<int>();
Console.WriteLine(numbers.Count); // 0
Console.WriteLine(numbers.Capacity); // 0 (initially)
// Adding elements
numbers.Add(1);
numbers.Add(2);
Console.WriteLine(numbers.Count); // 2
Console.WriteLine(numbers.Capacity); // 4 (resized automatically)
Lists typically double their capacity each time they run out of space. This amortized resizing strategy ensures good performance over time, even though an individual resize can be costly.
Although resizing involves creating a new larger array and copying elements, the doubling
strategy means that on average, adding elements to a list is still an
O(1)
operation.
Common List Methods
Lists provide a rich set of methods to manipulate elements. Some of the most useful include:
Contains(item)
- checks if an element exists.IndexOf(item)
- returns the index of the first occurrence.Sort()
- sorts elements in ascending order.Reverse()
- reverses the order of elements.Clear()
- removes all elements.
List<int> data = new List<int> { 3, 1, 4, 2 };
data.Sort(); // 1, 2, 3, 4
data.Reverse(); // 4, 3, 2, 1
Performance Considerations
Lists are efficient but not perfect for every scenario. Some key points to remember:
- Indexing and appending are
O(1)
on average. - Inserting/removing in the middle is
O(n)
because elements must be shifted. - Resizing is costly but happens infrequently thanks to the doubling strategy.
If you know the approximate number of elements you’ll need, initialize the list with a
capacity to avoid repeated resizing. For example:
new List<int>(1000)
.
Lists vs Arrays
Both lists and arrays have their place. Here’s a quick comparison:
- Arrays: Faster for fixed-size data, slightly less memory overhead, no resizing.
- Lists: Flexible, built-in methods, better for dynamic or unpredictable data sets.
For example, storing sensor readings for a single day (fixed count) might be best with an array, but storing chat messages in an app (unknown count) works better with a list.
Summary
List<T>
is one of the most versatile and widely used collections in C#.
It combines the speed of arrays with the flexibility of dynamic resizing, while also providing powerful built-in
methods. Understanding how lists manage count vs capacity, and their performance implications,
will help you use them effectively in real-world applications. In the next article, we’ll explore another
powerful collection - Dictionary<K,V>
, which introduces key-value pair
storage for even faster lookups.