C# LINQ Tutorial — Where, Select, OrderBy and More with Examples
If you have ever written a loop to filter a list, sort some data, or find a specific item in a collection — and thought there must be a cleaner way to do this — LINQ is exactly what you were looking for. Language Integrated Query (LINQ) is one of the most powerful features in C#, and once you understand it, you will wonder how you ever wrote code without it.
In this guide we will cover everything you need to know about LINQ in C# — from what it is and why it exists, to all the essential methods with real, practical examples you can use immediately in your own projects.
What Is LINQ?
LINQ stands for Language Integrated Query. It is a feature introduced in C# 3.0 that allows you to write queries directly in C# to filter, sort, transform, and aggregate data from any collection — whether it is a List, an array, a database, an XML file, or a JSON response.
Before LINQ, if you wanted to filter a list of students to find only those with a grade above 80, you would write something like this:
// Without LINQ — verbose and repetitive
List<Student> topStudents = new List<Student>();
foreach (Student s in students) {
if (s.Grade > 80) {
topStudents.Add(s);
}
}
// Then sort them
topStudents.Sort((a, b) => b.Grade.CompareTo(a.Grade));
With LINQ, the exact same result in one clean line:
// With LINQ — clean, readable, expressive
var topStudents = students
.Where(s => s.Grade > 80)
.OrderByDescending(s => s.Grade)
.ToList();
Same result, dramatically less code, and — importantly — far more readable. Anyone reading the LINQ version can understand what it does at a glance.
Query Syntax vs Method Syntax
LINQ has two different ways to write the same query. Both produce identical results — it is purely a matter of style preference.
Method Syntax (most commonly used)
var result = students
.Where(s => s.Grade > 80)
.OrderBy(s => s.Name)
.Select(s => s.Name)
.ToList();
Query Syntax (looks like SQL)
var result = (from s in students
where s.Grade > 80
orderby s.Name
select s.Name).ToList();
Most modern C# developers use method syntax because it is more flexible, chains naturally, and is consistent with how you use other C# features like lambda expressions. This guide will use method syntax throughout. Make sure you have this namespace imported:
using System.Linq;
Sample Data — Used Throughout This Guide
We will use these two classes and sample lists for all examples below:
public class Student {
public string Name { get; set; }
public int Grade { get; set; }
public string Subject { get; set; }
public int Age { get; set; }
}
public class Product {
public string Name { get; set; }
public double Price { get; set; }
public string Category { get; set; }
public int Stock { get; set; }
}
// Sample student list
List<Student> students = new List<Student>() {
new Student { Name = "Randhir", Grade = 92, Subject = "Math", Age = 20 },
new Student { Name = "Priya", Grade = 78, Subject = "Science", Age = 22 },
new Student { Name = "Amit", Grade = 85, Subject = "Math", Age = 21 },
new Student { Name = "Neha", Grade = 65, Subject = "English", Age = 19 },
new Student { Name = "Rahul", Grade = 91, Subject = "Science", Age = 23 },
new Student { Name = "Pooja", Grade = 55, Subject = "Math", Age = 20 },
};
// Sample product list
List<Product> products = new List<Product>() {
new Product { Name = "Laptop", Price = 75000, Category = "Electronics", Stock = 10 },
new Product { Name = "Phone", Price = 25000, Category = "Electronics", Stock = 25 },
new Product { Name = "Desk", Price = 8000, Category = "Furniture", Stock = 5 },
new Product { Name = "Chair", Price = 5000, Category = "Furniture", Stock = 15 },
new Product { Name = "Monitor", Price = 18000, Category = "Electronics", Stock = 8 },
};
Where() — Filter a Collection
Where() filters a collection based on a condition. It
returns only the elements where the condition is true — exactly like
a SQL WHERE clause.
// Get all students with grade above 80
var topStudents = students.Where(s => s.Grade > 80).ToList();
// Result: Randhir (92), Amit (85), Rahul (91)
// Get all Math students
var mathStudents = students.Where(s => s.Subject == "Math").ToList();
// Result: Randhir, Amit, Pooja
// Multiple conditions — students with grade > 80 AND aged under 22
var filtered = students
.Where(s => s.Grade > 80 && s.Age < 22)
.ToList();
// Result: Randhir (92, age 20), Amit (85, age 21)
// Products under 20000
var affordable = products.Where(p => p.Price < 20000).ToList();
// Result: Chair (5000), Monitor (18000), Desk (8000)
Select() — Transform and Project Data
Select() transforms each element in a collection into
something else — like SQL's SELECT column list. Use it to extract
specific fields or reshape your data.
// Get only student names (List<string> instead of List<Student>)
var names = students.Select(s => s.Name).ToList();
// Result: ["Randhir", "Priya", "Amit", "Neha", "Rahul", "Pooja"]
// Get names in uppercase
var upperNames = students.Select(s => s.Name.ToUpper()).ToList();
// Result: ["RANDHIR", "PRIYA", "AMIT", ...]
// Create an anonymous object with just the fields you need
var summary = students.Select(s => new {
s.Name,
s.Grade,
Passed = s.Grade >= 60
}).ToList();
foreach (var item in summary) {
Console.WriteLine($"{item.Name}: Grade {item.Grade}, Passed: {item.Passed}");
}
// Output:
// Randhir: Grade 92, Passed: True
// Priya: Grade 78, Passed: True
// Neha: Grade 65, Passed: True
// Pooja: Grade 55, Passed: False
OrderBy() and OrderByDescending() — Sort Data
// Sort students by grade — lowest to highest
var byGradeAsc = students.OrderBy(s => s.Grade).ToList();
// Pooja(55), Neha(65), Priya(78), Amit(85), Rahul(91), Randhir(92)
// Sort students by grade — highest to lowest
var byGradeDesc = students.OrderByDescending(s => s.Grade).ToList();
// Randhir(92), Rahul(91), Amit(85), Priya(78), Neha(65), Pooja(55)
// Sort by name alphabetically
var byName = students.OrderBy(s => s.Name).ToList();
// Amit, Neha, Pooja, Priya, Rahul, Randhir
// Sort by multiple fields — first by Subject, then by Grade descending
var multiSort = students
.OrderBy(s => s.Subject)
.ThenByDescending(s => s.Grade)
.ToList();
First(), FirstOrDefault(), Last(), LastOrDefault()
These methods retrieve a single element from a collection.
Understanding the difference between First() and
FirstOrDefault() is important — using the wrong one
can cause runtime exceptions.
// First() — throws InvalidOperationException if no match found
var topStudent = students.OrderByDescending(s => s.Grade).First();
// Returns: Randhir (grade 92)
// FirstOrDefault() — returns null (or default value) if no match
var mathGenius = students.FirstOrDefault(s => s.Subject == "Physics");
// Returns: null (no Physics students in our list)
// Always check for null when using FirstOrDefault
if (mathGenius != null) {
Console.WriteLine(mathGenius.Name);
}
// Single() — returns one result, throws if 0 or more than 1 match
var randhir = students.Single(s => s.Name == "Randhir");
// SingleOrDefault() — returns null if no match, throws if more than 1
var neha = students.SingleOrDefault(s => s.Name == "Neha");
// Last and LastOrDefault work the same way
var lowestGrade = students.OrderBy(s => s.Grade).Last();
// Returns: Randhir when sorted ascending... wait, that gives highest
// Returns Pooja (55) — the last item after sorting ascending
Rule of thumb: Always prefer
FirstOrDefault() over First() unless you
are 100% certain the element exists. An unexpected empty result
causes a silent null reference rather than a crashing exception —
which is much easier to handle gracefully.
Count(), Sum(), Min(), Max(), Average()
These aggregate methods compute a single value from a collection — exactly like SQL aggregate functions.
// Count — total number of students
int total = students.Count(); // 6
// Count with condition — students who passed (grade >= 60)
int passed = students.Count(s => s.Grade >= 60); // 5
// Sum — total of all grades
int totalGrades = students.Sum(s => s.Grade); // 466
// Average — average grade
double average = students.Average(s => s.Grade); // 77.67
// Min and Max — lowest and highest grade
int lowest = students.Min(s => s.Grade); // 55
int highest = students.Max(s => s.Grade); // 92
// Product examples
double totalValue = products.Sum(p => p.Price * p.Stock);
double averagePrice = products.Average(p => p.Price);
double cheapest = products.Min(p => p.Price); // 5000
double mostExpensive = products.Max(p => p.Price); // 75000
GroupBy() — Group Elements
GroupBy() groups elements by a key — like SQL's GROUP BY.
It returns a collection of groups, where each group has a key and
contains the matching elements.
// Group students by Subject
var bySubject = students.GroupBy(s => s.Subject);
foreach (var group in bySubject) {
Console.WriteLine($"\nSubject: {group.Key}");
foreach (var student in group) {
Console.WriteLine($" {student.Name} — Grade: {student.Grade}");
}
}
// Output:
// Subject: Math
// Randhir — Grade: 92
// Amit — Grade: 85
// Pooja — Grade: 55
//
// Subject: Science
// Priya — Grade: 78
// Rahul — Grade: 91
//
// Subject: English
// Neha — Grade: 65
// Group and get count per group
var subjectCount = students
.GroupBy(s => s.Subject)
.Select(g => new { Subject = g.Key, Count = g.Count() })
.ToList();
// Math: 3, Science: 2, English: 1
// Group products by category and get average price
var categoryAvg = products
.GroupBy(p => p.Category)
.Select(g => new {
Category = g.Key,
AveragePrice = g.Average(p => p.Price),
Count = g.Count()
}).ToList();
Any() and All()
These return boolean values — useful for quick checks on a collection without retrieving any actual data.
// Any() — returns true if AT LEAST ONE element matches
bool hasTopStudent = students.Any(s => s.Grade >= 90); // true
bool hasFailures = students.Any(s => s.Grade < 50); // false
bool hasPhysics = students.Any(s => s.Subject == "Physics"); // false
// Any() without condition — checks if collection is not empty
bool hasStudents = students.Any(); // true
// All() — returns true only if ALL elements match
bool allPassed = students.All(s => s.Grade >= 60); // false (Pooja has 55)
bool allAdults = students.All(s => s.Age >= 18); // true
Distinct() — Remove Duplicates
// Get unique subjects
var uniqueSubjects = students.Select(s => s.Subject).Distinct().ToList();
// Result: ["Math", "Science", "English"]
// Get unique ages
var uniqueAges = students.Select(s => s.Age).Distinct().OrderBy(a => a).ToList();
// Result: [19, 20, 21, 22, 23]
// Remove duplicates from a simple list
List<int> numbers = new List<int>() { 1, 2, 2, 3, 3, 3, 4 };
var unique = numbers.Distinct().ToList();
// Result: [1, 2, 3, 4]
Chaining Multiple LINQ Methods
The real power of LINQ comes from chaining methods together. Each
method returns an IEnumerable that the next method
operates on:
// Complex real-world query:
// Get the top 3 students from Math or Science
// who scored above 70, sorted by grade descending
// and return just their names and grades
var result = students
.Where(s => s.Subject == "Math" || s.Subject == "Science")
.Where(s => s.Grade > 70)
.OrderByDescending(s => s.Grade)
.Take(3) // Take only the first 3 results
.Select(s => new { s.Name, s.Grade })
.ToList();
foreach (var item in result) {
Console.WriteLine($"{item.Name}: {item.Grade}");
}
// Output:
// Randhir: 92
// Rahul: 91
// Amit: 85
Skip() and Take() — Pagination
Skip() and Take() are essential for
implementing pagination — showing a fixed number of results per page:
int pageNumber = 2;
int pageSize = 2;
var pagedStudents = students
.OrderBy(s => s.Name)
.Skip((pageNumber - 1) * pageSize) // Skip first page results
.Take(pageSize) // Take only current page items
.ToList();
// Page 1: Amit, Neha
// Page 2: Pooja, Priya ← current page
// Page 3: Rahul, Randhir
LINQ vs For Loop — When to Use Which
- Use LINQ when filtering, sorting, transforming, or aggregating data from a collection. It is more readable and expressive for these operations.
- Use a for loop when you need to modify elements in place, break out of iteration early for complex reasons, or work with indices directly.
-
Performance: For simple operations on small
collections, the difference is negligible. For extremely large
datasets with complex operations, benchmark both — LINQ has some
overhead from lambda delegates, but it also supports parallel
execution with
AsParallel().
Common LINQ Mistakes
1. Forgetting ToList() — deferred execution trap
// LINQ queries are lazy — they don't execute until you iterate them
var query = students.Where(s => s.Grade > 80); // NOT executed yet
students.Add(new Student { Name = "New", Grade = 95, Subject = "Math", Age = 18 });
// Now when we iterate, the new student IS included
foreach (var s in query) { // executes NOW, includes the new student
Console.WriteLine(s.Name);
}
// Fix — call ToList() to execute immediately and snapshot the result
var snapshot = students.Where(s => s.Grade > 80).ToList(); // executes NOW
2. Using First() instead of FirstOrDefault()
// ❌ Crashes if no Physics students exist
var physics = students.First(s => s.Subject == "Physics"); // InvalidOperationException
// ✅ Returns null safely
var physics = students.FirstOrDefault(s => s.Subject == "Physics");
if (physics != null) { Console.WriteLine(physics.Name); }
3. Multiple enumeration of the same query
// ❌ Bad — query executes twice (hits database twice if using EF)
var query = students.Where(s => s.Grade > 80);
int count = query.Count(); // first execution
var list = query.ToList(); // second execution
// ✅ Good — execute once, reuse the result
var list = students.Where(s => s.Grade > 80).ToList();
int count = list.Count;
Quick Reference — Most Used LINQ Methods
| Method | What It Does | Returns |
|---|---|---|
| Where() | Filters based on condition | IEnumerable |
| Select() | Transforms each element | IEnumerable |
| OrderBy() | Sorts ascending | IOrderedEnumerable |
| OrderByDescending() | Sorts descending | IOrderedEnumerable |
| GroupBy() | Groups elements by key | IGrouping |
| First() / FirstOrDefault() | Gets first matching element | Single element |
| Count() | Counts elements | int |
| Sum() / Average() | Aggregates numeric values | number |
| Any() / All() | Checks if condition is met | bool |
| Distinct() | Removes duplicates | IEnumerable |
| Skip() / Take() | Pagination | IEnumerable |
| ToList() / ToArray() | Executes query immediately | List / Array |
Final Thought
LINQ is one of those features that once you start using it, you cannot
imagine going back. It makes your code shorter, more readable, and
easier to maintain. The key is to start with the basics —
Where(), Select(), and
OrderBy() — and build from there. Once those feel natural,
the rest of the methods follow the same pattern.
The most important habit to develop is always using
FirstOrDefault() instead of First(), and
calling ToList() when you want to execute the query
immediately rather than defer it. Those two habits alone will save
you from the most common LINQ bugs.
Working on a C# project and need help writing a specific LINQ query? Drop us a message on our contact page and we will help you figure it out.