← Open in the full interactive course (progress tracking, search & more)

OrderBy doesn't sort in place — it hands you a new sequence, arranged the way you asked.

"Sort these by price" sounds simple, until you need "sort by department, then by name within each department" — a two-level sort a single comparison can't express cleanly. LINQ's sorting operators handle both the simple case and the layered case with the same small, composable vocabulary.

This lesson covers OrderBy/OrderByDescending for the primary sort key, ThenBy/ThenByDescending for tie-breaking secondary keys, and the guarantee of stable sorting that makes all of this predictable.

What Is It?

The Simple Explanation

OrderBy arranges a sequence in ascending order according to a key you choose from each item — cheapest first, earliest first, alphabetically first. OrderByDescending does the reverse. ThenBy/ThenByDescending chain onto either of those to break ties with a secondary (or third, or fourth) key.

The Technical Definition

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)

OrderBy doesn't take a full comparison — just a key selector, a function that extracts the value to sort by from each item. LINQ uses that key's default comparer (Comparer<TKey>.Default) to do the actual comparing, which is why it works out of the box for numbers, strings, dates, and any type implementing IComparable<T>. Notice the return type: IOrderedEnumerable<TSource>, not plain IEnumerable<TSource> — that more specific type is exactly what makes ThenBy available to chain afterward.

Why Does It Exist?

A hand-written sort means writing your own comparer, and multi-key sorting compounds the pain — a manual comparison function with nested tie-breaking logic gets messy fast:

// Manual multi-key sort employees.Sort((a, b) => { int deptCompare = string.Compare(a.Department, b.Department); if (deptCompare != 0) return deptCompare; return string.Compare(a.Name, b.Name); });
// OrderBy + ThenBy var sorted = employees .OrderBy(e => e.Department) .ThenBy(e => e.Name);

The LINQ version reads as a direct translation of the business requirement — "by department, then by name" — with no manual tie-breaking logic to get wrong. It also doesn't mutate the original collection, unlike List<T>.Sort(), which sorts in place and destroys the original order.

Big Picture

MULTI-KEY SORT, VISUALLY
Input: (Eng, Cara), (Sales, Ben), (Eng, Ana), (Sales, Ana)
↓ .OrderBy(e => e.Department) — groups by department, order within each group still arbitrary
(Eng, Cara), (Eng, Ana), (Sales, Ben), (Sales, Ana)
↓ .ThenBy(e => e.Name) — breaks ties within each department group
(Eng, Ana), (Eng, Cara), (Sales, Ana), (Sales, Ben)

How It Works

SORTING, STEP BY STEP
1. OrderBy SORTS BY ITS KEY FIRST
2. TIES ARE LEFT IN THEIR ORIGINAL RELATIVE ORDER
3. EACH ThenBy REFINES TIES FROM THE PREVIOUS STEP

Simple Example

public record Employee(int Id, string Name, string Department, decimal Salary); List<Employee> employees = [ new(1, "Cara", "Engineering", 95000m), new(2, "Ben", "Sales", 72000m), new(3, "Ana", "Engineering", 88000m), new(4, "Dev", "Sales", 80000m), new(5, "Ella", "Engineering", 88000m), ]; // Single key, ascending var byName = employees.OrderBy(e => e.Name); // Single key, descending var bySalaryDesc = employees.OrderByDescending(e => e.Salary); // Multi-key: department first, then salary descending within each department var report = employees .OrderBy(e => e.Department) .ThenByDescending(e => e.Salary); foreach (var e in report) Console.WriteLine($"{e.Department}: {e.Name} — ${e.Salary}"); // Engineering: Cara — $95000 // Engineering: Ana — $88000 (tied with Ella on salary, Ana came first in source) // Engineering: Ella — $88000 // Sales: Dev — $80000 // Sales: Ben — $72000

Code → Meaning → Result: Notice Ana appears before Ella despite an identical $88000 salary — that's the stable sort guarantee: since ThenByDescending found them tied, it left them in their original relative order from the source list (Ana came before Ella in employees).

Real-World Example

An admin dashboard shows a leaderboard of top customers by total spend, and within a tie, alphabetically by name for a deterministic, presentable order:

public record CustomerSpend(string Name, decimal TotalSpent); List<CustomerSpend> spending = [ new("Cara Lopez", 4200m), new("Ana Cole", 4200m), new("Ben Diaz", 3100m), ]; var leaderboard = spending .OrderByDescending(c => c.TotalSpent) .ThenBy(c => c.Name); int rank = 1; foreach (var c in leaderboard) Console.WriteLine($"#{rank++} {c.Name} — ${c.TotalSpent}"); // #1 Ana Cole — $4200 // #2 Cara Lopez — $4200 // #3 Ben Diaz — $3100

Without the ThenBy(c => c.Name) tie-breaker, Ana and Cara's relative order for that tied $4200 would depend entirely on their original order in spending — fine for one run, but fragile and confusing if the underlying data source (say, a database query without an explicit order) ever returns rows in a different sequence. Explicit tie-breaking keys make sort output deterministic and reproducible.

Analogy

Filing Cabinets, Drawer by Drawer

Think of OrderBy as sorting files into drawers by department — all "Engineering" files together, all "Sales" files together, each drawer in department order. ThenBy is then organizing the folders within each drawer alphabetically. You never mix files between drawers while doing the within-drawer sort — that's exactly why ThenBy only ever breaks ties from the level above it, never overriding it.

Under the Hood

STABLE SORTING AND IOrderedEnumerable<T>
1. LINQ'S SORT IS GUARANTEED STABLE
2. WHY ThenBy NEEDS IOrderedEnumerable<T>, NOT PLAIN IEnumerable<T>
3. EXECUTION IS DEFERRED, BUT THE SORT ITSELF NEEDS THE WHOLE SEQUENCE

Common Confusion

1. Chaining two OrderBy calls vs. OrderBy then ThenBy

These look similar but behave very differently: list.OrderBy(a).OrderBy(b) sorts by a, then completely re-sorts by b — the second call discards the first ordering entirely, since each OrderBy call independently sorts the whole sequence. list.OrderBy(a).ThenBy(b) sorts by a primarily, using b only to break ties. If you want a true multi-key sort, always use ThenBy/ThenByDescending after the first OrderBy, never a second OrderBy.

2. OrderBy vs List<T>.Sort()

List<T>.Sort() (from Foundations) sorts the list in place, mutating the original, and returns void. OrderBy never touches the source — it returns a brand-new sequence, leaving the original list's order untouched. Also worth knowing: List<T>.Sort() is not guaranteed stable, while LINQ's OrderBy is.

Common Mistakes

Mistake 1 — Chaining a second OrderBy instead of ThenBy

employees.OrderBy(e => e.Department).OrderBy(e => e.Name) sorts by name only — the department ordering is completely discarded by the second call. employees.OrderBy(e => e.Department).ThenBy(e => e.Name).

Mistake 2 — Assuming OrderBy mutates the source

Calling employees.OrderBy(e => e.Name); without capturing the result and expecting employees itself to now be sorted. OrderBy always returns a new sequence: var sorted = employees.OrderBy(e => e.Name);.

When Should I Use It?

Use OrderBy / ThenBy when

Consider alternatives when

Mental Model

OrderBy = "sort by this key first"
ThenBy = "when tied on everything before me, sort by this key next"

Remember:
· A second OrderBy re-sorts from scratch; only ThenBy layers on top.
· LINQ's sort is stable — ties keep their original relative order unless you add another tie-breaking key.
· OrderBy never mutates the source; it always returns a new sequence.

Key Takeaway


Check Your Understanding

You've learned how to sort sequences by one or more keys. Let's check your understanding.

1. What is the difference between list.OrderBy(a).OrderBy(b) and list.OrderBy(a).ThenBy(b)?

Show answer

Correct: B

Why B is correct: As covered in Common Confusion, a second OrderBy is a fresh, independent sort — it has no memory of the previous one. Only ThenBy layers a tie-break on top of an existing order.

Why A is incorrect: This is exactly the mistake the lesson warns about — they behave very differently.

Why C is incorrect: ThenBy works with any key type that has a natural or default comparer, not just numbers.

Why D is incorrect: Both chains compile fine — the issue is behavior, not syntax.

Reinforcement: Always use ThenBy, never a second OrderBy, when you want a true multi-key sort.

2. Two employees have the identical salary. After sorting with employees.OrderByDescending(e => e.Salary) (no ThenBy), what determines their relative order in the result?

Show answer

Correct: B

Why B is correct: As explained in Under the Hood, LINQ's sort is a documented, guaranteed stable sort — items comparing equal on the given key(s) retain their original relative order from the source.

Why A is incorrect: Stability is a documented .NET guarantee, not undefined behavior — this is exactly the point of "stable."

Why C is incorrect: LINQ never adds an implicit fallback sort key — if you want name as a tie-breaker, you must add .ThenBy(e => e.Name) explicitly.

Why D is incorrect: No such automatic behavior exists — Id plays no role unless you explicitly sort by it.

Reinforcement: Stability means "ties preserve source order" — not "ties are broken by some other implicit rule."

3. Why does OrderBy return IOrderedEnumerable<T> instead of plain IEnumerable<T>?

Show answer

Correct: B

Why B is correct: This is exactly what makes chained multi-key sorting work correctly — the more specific return type is what exposes the ThenBy/ThenByDescending methods and gives them the context of "already sorted by this."

Why A is incorrect: The distinct type serves a real, functional purpose, as explained.

Why C is incorrect: IOrderedEnumerable<T> has nothing to do with indexing — like IEnumerable<T>, it only supports sequential enumeration.

Why D is incorrect: Any IEnumerable<T>, ordered or not, can generally be enumerated multiple times if the underlying source allows it — that's unrelated to why this specific return type exists.

Reinforcement: The specific return type is what makes ThenBy type-safe and meaningful — it's a deliberate design choice, not incidental.

4. You need a customer leaderboard sorted by total spend (highest first), with alphabetical order by name as a deterministic tie-breaker. Which LINQ expression is correct?

Show answer

Correct: B

Why B is correct: OrderByDescending establishes the primary sort (highest spend first); ThenBy correctly layers alphabetical order as the tie-breaker without disturbing the primary sort.

Why A is incorrect: The second OrderBy discards the spend-based sort entirely, resulting in a purely alphabetical list.

Why C is incorrect: This is invalid — ThenByDescending can only be called on an IOrderedEnumerable<T> (the result of a prior OrderBy/OrderByDescending), not as the very first operator in a chain; it wouldn't compile.

Why D is incorrect: Like option A, the second OrderByDescending here discards the name-based sort — the final result would be sorted purely by spend, with no meaningful tie-break at all.

Reinforcement: Establish the primary key with OrderBy/OrderByDescending, then layer every additional key with ThenBy/ThenByDescending — never a second OrderBy.

You can now sort by one or more keys with confidence. Next up: grouping related items together with GroupBy.


dotnetmadeeasy.com — Learn C# and .NET, the right way.