Programming
Object-Oriented Programming
Data Structures
List Sorting
Software Development

How to Sort a List<T> by a property in the object

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Sorting a List<T> by object properties is a common C# task, but there are several correct approaches depending on your goals. If you want readable code and easy multi-level sorting, LINQ is usually best. If you need in-place sorting with less allocation, List<T>.Sort is faster and more memory efficient. Problems typically come from null handling, inconsistent comparers, and accidental string-based numeric sorting. This guide shows practical patterns for property-based sorting, including ascending and descending order, multiple keys, and dynamic property names.

Choose Between OrderBy and List<T>.Sort

LINQ creates a new ordered sequence and is very expressive:

csharp
1var users = new List<User>
2{
3    new User { Id = 3, Name = "Mia", Score = 81 },
4    new User { Id = 1, Name = "Alex", Score = 95 },
5    new User { Id = 2, Name = "Zoe", Score = 81 }
6};
7
8var byScoreThenName = users
9    .OrderBy(u => u.Score)
10    .ThenBy(u => u.Name)
11    .ToList();

List<T>.Sort modifies the same list and can reduce allocations:

csharp
users.Sort((a, b) => a.Score.CompareTo(b.Score));

If the list is large and you do not need the original order preserved, in-place sort is often the better choice.

Build Safe Property Comparers

Custom comparers let you control nulls, case sensitivity, and secondary keys. This is important when data quality is uneven.

csharp
1users.Sort((a, b) =>
2{
3    int scoreCompare = a.Score.CompareTo(b.Score);
4    if (scoreCompare != 0) return scoreCompare;
5
6    return string.Compare(
7        a.Name,
8        b.Name,
9        StringComparison.OrdinalIgnoreCase
10    );
11});

For descending sort, reverse the comparison:

csharp
users.Sort((a, b) => b.Score.CompareTo(a.Score));

For nullable values, avoid direct CompareTo if either side may be null:

csharp
users.Sort((a, b) => Nullable.Compare(a.LastLoginTicks, b.LastLoginTicks));

These details prevent runtime exceptions and unstable output.

Dynamic Sorting by Property Name

APIs and admin screens often receive sort columns dynamically (for example, "Name" or "Score"). Reflection can support this, but validate input to avoid runtime surprises.

csharp
1public static List<T> SortByProperty<T>(
2    List<T> source,
3    string property,
4    bool descending = false)
5{
6    var prop = typeof(T).GetProperty(property);
7    if (prop == null)
8        throw new ArgumentException($"Unknown property: {property}");
9
10    return descending
11        ? source.OrderByDescending(x => prop.GetValue(x, null)).ToList()
12        : source.OrderBy(x => prop.GetValue(x, null)).ToList();
13}

For high-throughput code, expression trees or precompiled delegates perform better than repeated reflection.

Verify Correctness with Tests

Sorting bugs can be subtle, especially when values tie. Write small tests for edge cases.

csharp
1[Fact]
2public void SortByScoreThenName_IsDeterministic()
3{
4    var users = new List<User>
5    {
6        new User { Name = "bob", Score = 10 },
7        new User { Name = "Alice", Score = 10 }
8    };
9
10    var result = users.OrderBy(u => u.Score)
11                      .ThenBy(u => u.Name, StringComparer.OrdinalIgnoreCase)
12                      .ToList();
13
14    Assert.Equal("Alice", result[0].Name);
15}

This protects behavior when comparers are modified later.

Practical Verification Workflow

A reliable way to avoid regressions is to validate the solution in three passes: baseline, controlled change, and repeatability check. First, capture a baseline outcome before you apply fixes. This could be a failing command, a wrong output sample, a stack trace, or a screenshot of current behavior. Second, apply one focused change and rerun exactly the same checks so you can attribute improvements to a specific edit. Third, rerun the checks multiple times or with slightly different inputs to ensure the fix is not accidental or data-specific.

A lightweight template you can adapt for most projects looks like this:

bash
1# 1) reproduce current behavior
2./run_example.sh > before.txt
3
4# 2) apply your change
5# edit config/code based on this article
6
7# 3) verify behavior after change
8./run_example.sh > after.txt
9diff -u before.txt after.txt

If your environment involves tests, add at least one focused regression test that would fail before the fix and pass after it. This turns a one-time troubleshooting success into a durable maintenance improvement, which is especially important when teams rotate ownership or upgrade dependencies later.

Common Pitfalls

  • Sorting numeric values stored as strings, which produces lexical order ("100" before "20").
  • Forgetting a secondary key, causing unstable order among equal primary values.
  • Using culture-sensitive string comparison unintentionally in backend logic.
  • Calling OrderBy and expecting the original list to mutate in place.
  • Accepting arbitrary property names without validation, leading to runtime errors.

Summary

To sort List<T> by property, use LINQ for clarity and List<T>.Sort for in-place performance. Define comparers that handle nulls and ties explicitly, and test edge cases to guarantee deterministic order. If you support dynamic property names, validate them early and prefer precompiled accessors in performance-critical paths.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms