C# LINQ Tutorial — Where, Select, OrderBy and More with Examples

👁️ 2 Views
|
📅 Jul 16, 2026
|
⏱️ 15 min read
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.