List in C#

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

What Is List<T> in C#?

List<T> is a generic collection in C# that represents a dynamic array. It stores elements in an indexed sequence and can automatically grow as new elements are added.

Unlike a fixed-size C# array, a List<T> does not require the final number of elements to be known when the collection is created. It is commonly used when an application needs an ordered collection whose size can change during execution.

Creating a List<T>

To use List<T>, include the System.Collections.Generic namespace. The type parameter T specifies the type of elements that the list can contain.

using System.Collections.Generic;

List<int> numbers = new List<int>();

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

The list above can contain only int values. Generic typing provides compile-time type safety.

Initializing a List with Values

A list can be initialized with values when it is created.

var fruits = new List<string>
{
    "Apple",
    "Banana",
    "Orange"
};

Console.WriteLine(fruits[0]);
// Apple

The elements are stored in insertion order and can be accessed using zero-based indexes.

Adding Elements with Add()

The Add() method appends an element to the end of the list. If the internal storage has no available capacity, the list can allocate a larger backing array and copy its existing elements.

var numbers = new List<int>();

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

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

Adding Multiple Elements with AddRange()

AddRange() adds multiple elements from another collection to the end of the list.

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

numbers.AddRange(new[] { 30, 40, 50 });

Console.WriteLine(string.Join(", ", numbers));
// 10, 20, 30, 40, 50

Inserting Elements with Insert()

The Insert() method places an element at a specified index. Existing elements from that position onward are shifted to make room.

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

numbers.Insert(2, 30);

Console.WriteLine(string.Join(", ", numbers));
// 10, 20, 30, 40

Inserting near the beginning of a large list can require many elements to be shifted, making the operation O(n) in the general case.

Accessing Elements by Index

A List<T> provides direct indexed access. Because the collection is backed by an array, accessing an element by a valid index is typically O(1).

var languages = new List<string>
{
    "C#",
    "Java",
    "Python",
    "JavaScript"
};

Console.WriteLine(languages[2]);
// Python

Updating an Element

An existing element can be replaced by assigning a new value to its index.

var languages = new List<string>
{
    "C#",
    "Java",
    "Python"
};

languages[1] = "TypeScript";

Console.WriteLine(string.Join(", ", languages));
// C#, TypeScript, Python

Removing Elements from a List

C# provides several methods for removing elements depending on whether you want to remove by value, index, or condition.

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

numbers.Remove(20);
numbers.RemoveAt(1);

Console.WriteLine(string.Join(", ", numbers));
// 10, 40

Remove() removes the first matching value, while RemoveAt() removes the element at a specified index.

RemoveRange() and Clear()

RemoveRange() removes a specified number of elements starting at an index. Clear() removes all elements from the list.

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

numbers.RemoveRange(1, 2);

Console.WriteLine(string.Join(", ", numbers));
// 10, 40, 50

numbers.Clear();

Console.WriteLine(numbers.Count);
// 0

Checking Whether a List Contains an Element

The Contains() method checks whether a value exists in the list.

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

Console.WriteLine(numbers.Contains(20));
// True

Console.WriteLine(numbers.Contains(50));
// False

For a general List<T>, Contains() performs a linear search and therefore takes O(n)

Finding Elements with Find()

Find() returns the first element that satisfies a specified condition. This is particularly useful when a list contains objects.

var numbers = new List<int>
{
    5,
    12,
    18,
    25
};

int result = numbers.Find(number => number > 15);

Console.WriteLine(result);
// 18

FindIndex() and FindAll()

FindIndex() returns the index of the first matching element, while FindAll() returns all elements that satisfy a condition.

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

int index = numbers.FindIndex(number => number == 20);

var evenNumbers = numbers.FindAll(number => number % 2 == 0);

Console.WriteLine(index);
// 2

Console.WriteLine(string.Join(", ", evenNumbers));
// 10, 20, 30

Iterating Through a List

A list can be traversed using foreach, a traditional for loop, or LINQ when a transformation or filtering operation is required.

var languages = new List<string>
{
    "C#",
    "Java",
    "Python"
};

foreach (var language in languages)
{
    Console.WriteLine(language);
}

Use a for loop when the index itself is important.

for (int i = 0; i < languages.Count; i++)
{
    Console.WriteLine($"{i}: {languages[i]}");
}

Sorting a List

The Sort() method sorts the elements of a list according to their default comparison behavior or a supplied comparer.

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

numbers.Sort();

Console.WriteLine(string.Join(", ", numbers));
// 10, 20, 30, 40

Sorting in Descending Order

A custom comparer can be passed to Sort() when a different ordering is required.

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

numbers.Sort((a, b) => b.CompareTo(a));

Console.WriteLine(string.Join(", ", numbers));
// 40, 30, 20, 10

List Capacity and Count

Count and Capacity describe different aspects of a List<T>.

  • Count: number of elements currently stored.
  • Capacity: amount of storage allocated internally for elements.
var numbers = new List<int>();

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

Console.WriteLine($"Count: {numbers.Count}");
Console.WriteLine($"Capacity: {numbers.Capacity}");

Capacity may be larger than count because the list keeps additional internal storage available for future additions.

Setting an Initial Capacity

If the approximate number of elements is already known, an initial capacity can be supplied to reduce the number of internal reallocations while the list grows.

var users = new List<string>(100);

Console.WriteLine(users.Capacity);
// 100

Initial capacity does not add 100 elements to the list. The Count remains zero until values are actually added.

TrimExcess()

A list may have more capacity than it currently needs. The TrimExcess() method can be used when you want to reduce excess allocated capacity.

var numbers = new List<int>(100);

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

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

numbers.TrimExcess();

Console.WriteLine(numbers.Capacity);

Trimming capacity is not something that should normally be performed after every removal. It is more appropriate when the collection has reached a stable size and reducing unused storage is useful.

Converting a List to an Array

The ToArray() method creates an array containing the current elements of the list.

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

int[] array = numbers.ToArray();

Console.WriteLine(array.Length);
// 3

Working with Objects

List<T> is especially useful for storing collections of custom objects.

class Student
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

var students = new List<Student>
{
    new Student { Id = 1, Name = "Asha" },
    new Student { Id = 2, Name = "Rahul" },
    new Student { Id = 3, Name = "Priya" }
};

var student = students.Find(s => s.Id == 2);

Console.WriteLine(student?.Name);
// Rahul

Filtering a List with LINQ

LINQ can be used with List<T> to filter, project, group, and transform collection data.

using System.Linq;

var numbers = new List<int>
{
    5,
    10,
    15,
    20,
    25
};

var result = numbers
    .Where(number => number >= 15)
    .ToList();

Console.WriteLine(string.Join(", ", result));
// 15, 20, 25

List<T> Time Complexity

Because List<T> is backed by an array, its common operations have predictable performance characteristics.

Operation Typical Complexity
Access by index O(1)
Update by index O(1)
Add at end O(1) amortized
Insert at beginning/middle O(n)
Remove by index O(n) in general
Contains O(n)
Find O(n)

When Should You Use List<T>?

List<T> is a good choice when you need an ordered, resizable collection and frequently access elements by index or append values to the end.

  • Lists of users or products.
  • Search results and API response collections.
  • Application records that grow during execution.
  • Collections that require indexed access.
  • Data that needs sorting or filtering.
  • Temporary collections used during processing.

List<T> vs Array

Both arrays and List<T> provide indexed access, but their sizing behavior is different.

Feature Array List<T>
Size Fixed Resizable
Index access O(1) O(1)
Append Not directly supported beyond capacity O(1) amortized
Insertion Requires shifting or a new array Supported with methods such as Insert()
Convenience Simple fixed storage Rich collection API

Common List<T> Mistakes

  • Confusing Count with Capacity.
  • Assuming Contains() performs constant-time lookup.
  • Frequently inserting at the beginning of a large list without considering shifting costs.
  • Calling TrimExcess() repeatedly during frequent modifications.
  • Using a list when key-based lookup would be better handled by a dictionary.
  • Using a list when uniqueness is the primary requirement instead of using a hash set.
  • Accessing an invalid index and causing an ArgumentOutOfRangeException.

Practical Example: Managing Developer Skills

A practical application can use List<T> to maintain an ordered collection of skills. Elements can be added, searched, filtered, and sorted as requirements change.

var skills = new List<string>
{
    "C#",
    "ASP.NET Core",
    "SQL"
};

skills.Add("Azure");
skills.Add("Docker");

var hasCSharp = skills.Contains("C#");

var backendSkills = skills
    .Where(skill => skill.Contains("ASP.NET") || skill == "C#")
    .ToList();

skills.Sort();

Console.WriteLine($"Has C#: {hasCSharp}");
Console.WriteLine(string.Join(", ", backendSkills));
Console.WriteLine(string.Join(", ", skills));

Key Takeaways

  • List<T> is a generic, resizable collection in C#.
  • It provides fast indexed access through an array-backed implementation.
  • Add() appends an element and is O(1) amortized.
  • Insert() may require shifting elements and is O(n) in general.
  • Remove() removes by value, while RemoveAt() removes by index.
  • Count represents the number of stored elements.
  • Capacity represents allocated internal storage.
  • Contains() and Find() generally require linear search.
  • List<T> is a practical default for many ordered, resizable collections.

Code Examples

using System;
using System.Collections.Generic;

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

numbers.Add(50);
numbers.Insert(2, 30);
numbers[0] = 5;

Console.WriteLine(string.Join(", ", numbers));
// 5, 20, 30, 40, 50
    
using System;
using System.Collections.Generic;
using System.Linq;

var numbers = new List<int>
{
    5,
    10,
    15,
    20,
    25
};

var firstLargeNumber = numbers.Find(n => n > 15);

var evenNumbers = numbers
    .Where(n => n % 2 == 0)
    .ToList();

Console.WriteLine(firstLargeNumber);
// 20

Console.WriteLine(string.Join(", ", evenNumbers));
// 10, 20
    
using System;
using System.Collections.Generic;

var users = new List<string>(10);

users.Add("Asha");
users.Add("Rahul");
users.Add("Priya");

Console.WriteLine($"Count: {users.Count}");
Console.WriteLine($"Capacity: {users.Capacity}");

users.TrimExcess();

Console.WriteLine($"After TrimExcess: {users.Capacity}");
    

Pro Tip

Use List<T> when you need an ordered, resizable collection with frequent indexed access or appends. If your main operation is key-based lookup, uniqueness, FIFO processing, or LIFO processing, consider Dictionary<TKey, TValue>, HashSet<T>, Queue<T>, or Stack<T> instead.

🎨 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.