
Learn C# LINQ with 5 practical examples every developer uses daily. Master Where, Select, GroupBy & more with runnable code. Start writing cleaner C# today!
C# LINQ (Language Integrated Query) is one of the most powerful features in the .NET ecosystem, and learning it well will instantly make you a faster, cleaner C# developer. If you've ever written a tangled foreach loop just to filter a list, group some records, or pull a single field out of a collection, then this C# LINQ tutorial is for you. In this guide you'll learn LINQ through 5 practical examples that working developers in the USA, UK, Canada, and Australia use every single day.
By the end, you'll understand not just how to write a LINQ query in C#, but why it works the way it does — including deferred execution, the difference between method syntax and query syntax, and the common pitfalls that trip up beginners and intermediates alike.
What Is C# LINQ and Why Should You Use It?
LINQ is a set of extension methods (and a query syntax) that lets you query in-memory collections, databases, XML, and more using a single, consistent, readable API. Instead of writing imperative loops that describe how to get a result step by step, LINQ lets you write declarative code that describes what result you want.
Consider the difference. Here is the old, loop-based approach:
var activeUsers = new List<User>();
foreach (var user in users)
{
if (user.IsActive)
{
activeUsers.Add(user);
}
}
And here is the same logic with C# LINQ:
var activeUsers = users.Where(u => u.IsActive).ToList();
One line instead of eight. It reads almost like English, there's less room for bugs, and the intent is crystal clear. To use LINQ in any file, just add using System.Linq; at the top — it works on any type that implements IEnumerable<T>, which includes arrays, lists, dictionaries, and more.
For all examples below, assume we have this simple model and sample data:
public record Product(int Id, string Name, string Category, decimal Price, int Stock);
var products = new List<Product>
{
new(1, "Laptop", "Electronics", 1200m, 5),
new(2, "Mouse", "Electronics", 25m, 0),
new(3, "Desk", "Furniture", 300m, 12),
new(4, "Chair", "Furniture", 150m, 0),
new(5, "Monitor", "Electronics", 400m, 8),
new(6, "Notebook", "Stationery", 3m, 200)
};
Example 1: Filtering Data with Where (the Most Common LINQ Query)
The Where method is the bread and butter of every LINQ query in C#. It takes a predicate — a function that returns true or false — and returns only the elements that match.
// Get all products that are in stock and cost less than $500
var affordableInStock = products
.Where(p => p.Stock > 0 && p.Price < 500m)
.ToList();
foreach (var p in affordableInStock)
Console.WriteLine($"{p.Name} - ${p.Price}");
// Output:
// Desk - $300
// Monitor - $400
// Notebook - $3
Why it works this way: Where uses deferred execution. The filtering doesn't actually happen when you call Where — it happens when you enumerate the result (here, via ToList() or the foreach). This is a key LINQ concept that saves performance because the query only runs when you genuinely need the data.
Common pitfall: Forgetting to materialize the query. If you store a Where result and the underlying collection changes before you enumerate it, you'll get the new data, not a snapshot. Call .ToList() or .ToArray() when you need a stable copy.
Example 2: Transforming Data with Select (Projection)
Often you don't want whole objects — you want just one field, or a reshaped object. That's what Select (projection) is for. This is the second-most-used LINQ method after Where.
// Extract just the product names
var names = products.Select(p => p.Name).ToList();
// Project into a new anonymous shape for an API response
var summary = products
.Where(p => p.Stock > 0)
.Select(p => new
{
p.Name,
PriceWithTax = Math.Round(p.Price * 1.08m, 2)
})
.ToList();
foreach (var item in summary)
Console.WriteLine($"{item.Name}: ${item.PriceWithTax}");
Why use Select? Projecting to a smaller shape (a DTO or anonymous type) is a best practice when sending data over the network or to a UI — you avoid leaking internal fields and reduce payload size. When using LINQ with Entity Framework, Select also tells the database to fetch only the columns you need, which is a major performance win.
Tip: If your projection returns a collection per element (e.g., each order has many items), use SelectMany instead of Select to flatten the nested collections into one sequence.
Example 3: Sorting with OrderBy and ThenBy
Sorting is a daily task, and LINQ makes multi-level sorting trivial with OrderBy, OrderByDescending, and ThenBy.
// Sort by category alphabetically, then by price high-to-low
var sorted = products
.OrderBy(p => p.Category)
.ThenByDescending(p => p.Price)
.ToList();
foreach (var p in sorted)
Console.WriteLine($"{p.Category,-12} {p.Name,-10} ${p.Price}");
Why ThenBy matters: A common beginner mistake is chaining OrderBy(...).OrderBy(...), which actually re-sorts the entire sequence and discards the first sort. Always use ThenBy / ThenByDescending for secondary sort keys. The first OrderBy establishes the primary order; each ThenBy breaks ties.
Example 4: Grouping Data with GroupBy
When you need to bucket items — say, totaling sales per category or counting users per country — GroupBy is your friend. This is where LINQ really shines compared to manual loops with dictionaries.
// Group products by category and calculate stats for each group
var byCategory = products
.GroupBy(p => p.Category)
.Select(g => new
{
Category = g.Key,
Count = g.Count(),
TotalValue = g.Sum(p => p.Price * p.Stock),
AveragePrice = g.Average(p => p.Price)
})
.OrderByDescending(x => x.TotalValue)
.ToList();
foreach (var c in byCategory)
Console.WriteLine($"{c.Category}: {c.Count} items, inventory value ${c.TotalValue}");
// Output:
// Electronics: 3 items, inventory value $9200
// Furniture: 2 items, inventory value $3600
// Stationery: 1 items, inventory value $600
Why this is powerful: Each group is an IGrouping<TKey, TElement>, which exposes the Key and is itself enumerable. That means you can run aggregate functions — Count, Sum, Average, Min, Max — directly on each group. Doing this manually would require a dictionary, conditional initialization, and running totals: easily 15+ lines that's harder to read and easier to get wrong.
Example 5: Getting Single Results with First, Single, and Any
Sometimes you need exactly one element or a yes/no answer. These LINQ methods are essential for everyday logic, but they have important behavioral differences you must understand.
// Safely get the first match, or null if none exists
Product? firstCheap = products.FirstOrDefault(p => p.Price < 50m);
// Throws if zero OR more than one match - use when exactly one is expected
Product laptop = products.Single(p => p.Name == "Laptop");
// Boolean checks - fast and readable
bool hasOutOfStock = products.Any(p => p.Stock == 0); // true
bool allHavePrices = products.All(p => p.Price > 0); // true
// Aggregate helpers
decimal mostExpensive = products.Max(p => p.Price); // 1200
int totalStock = products.Sum(p => p.Stock); // 225
Why choose the right method: This is one of the most important LINQ best practices.
First()/Single()throw an exception if nothing matches — use them only when an empty result is genuinely a bug.FirstOrDefault()/SingleOrDefault()returndefault(null for reference types) instead — the safer choice for optional lookups.Singleadditionally throws if there's more than one match, which is perfect for enforcing uniqueness (like looking up by a primary key).Any()is far more efficient thanCount() > 0for checking existence, because it stops at the first match instead of iterating the entire collection.
Method Syntax vs Query Syntax in LINQ
Everything above used method syntax (the fluent .Where().Select() chain). LINQ also offers query syntax, which looks more like SQL:
var query =
from p in products
where p.Stock > 0
orderby p.Price descending
select p.Name;
Both compile to the exact same thing. Method syntax is more common in real-world C# code and supports every operator, while query syntax can be more readable for complex joins and groupings. Use whichever your team prefers — most developers favor method syntax for its consistency.
LINQ Best Practices and Common Pitfalls
- Avoid multiple enumeration. If you'll use a query result more than once, call
.ToList()once and reuse it. Re-enumerating a deferred query runs the whole pipeline again. - Filter before you project. Put
WherebeforeSelectso you do less transformation work and, in Entity Framework, generate tighter SQL. - Be careful with LINQ-to-Entities. Not every C# method can be translated to SQL. Calling unsupported methods triggers runtime errors or silent client-side evaluation — a major performance trap.
- Prefer
Any()overCount() > 0for existence checks. - Use
FirstOrDefaultoverFirstunless an empty sequence should be treated as an error.
Conclusion: Master C# LINQ to Write Cleaner Code Faster
You've now seen the 5 LINQ operations you'll reach for almost every day: filtering with Where, transforming with Select, sorting with OrderBy/ThenBy, grouping with GroupBy, and fetching single results with First/Single/Any. Together, these cover the vast majority of real-world data manipulation in C# applications.
Key takeaways from this C# LINQ tutorial:
- LINQ replaces verbose loops with readable, declarative queries.
- Deferred execution means queries run only when enumerated — materialize with
ToList()when needed. - Choose the safe variants (
FirstOrDefault,Any) to avoid exceptions and improve performance. - Method syntax and query syntax compile to the same code — pick what reads best.
The best way to learn LINQ is to use it. Take a foreach loop in your current project and try rewriting it as a LINQ query in C# today — you'll be surprised how much cleaner your code becomes. Happy coding!
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment