How to find elements in a list with LINQ in C#
LINQ provides several methods for finding elements in a collection: First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, Any, All, and Find. Choosing the right one avoids InvalidOperationException and keeps your intent clear.
The key distinction is what happens when no element matches — some methods throw, others return the type's default value (null for reference types, 0 / false for value types).
First vs FirstOrDefault
C# Example Code
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// First — throws InvalidOperationException if no match
int first = numbers.First(n => n > 3);
Console.WriteLine(first); // 4
// FirstOrDefault — returns 0 (default int) if no match
int notFound = numbers.FirstOrDefault(n => n > 100);
Console.WriteLine(notFound); // 0
// FirstOrDefault with a fallback value (C# 10+)
int withFallback = numbers.FirstOrDefault(n => n > 100, -1);
Console.WriteLine(withFallback); // -1Last vs LastOrDefault
C# Example Code
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Last — throws if no match
int last = numbers.Last(n => n < 4);
Console.WriteLine(last); // 3
// LastOrDefault — returns default if no match
int lastOrDefault = numbers.LastOrDefault(n => n > 100);
Console.WriteLine(lastOrDefault); // 0
// Without predicate
Console.WriteLine(numbers.Last()); // 5
Console.WriteLine(numbers.LastOrDefault()); // 5Single vs SingleOrDefault
Use Single when you expect exactly one match. It throws if there are zero or more than one.
C# Example Code
var users = new List<User>
{
new User(1, "Alice"),
new User(2, "Bob"),
new User(3, "Charlie")
};
// Single — throws if 0 or 2+ matches
var alice = users.Single(u => u.Id == 1);
Console.WriteLine(alice.Name); // Alice
// SingleOrDefault — returns null if 0 matches, throws if 2+ matches
var missing = users.SingleOrDefault(u => u.Id == 99);
Console.WriteLine(missing); // null (no exception)
record User(int Id, string Name);Find (List<T> method)
Find() is a List<T> method (not LINQ) that returns the first match or default. It's slightly faster than FirstOrDefault for List<T> because it avoids LINQ overhead.
C# Example Code
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// List.Find — only on List<T>
int found = numbers.Find(n => n > 3);
Console.WriteLine(found); // 4
// FindIndex — returns the index instead of the value
int index = numbers.FindIndex(n => n > 3);
Console.WriteLine(index); // 3
// FindAll — returns all matches (like Where().ToList())
var all = numbers.FindAll(n => n > 3);
Console.WriteLine(string.Join(", ", all)); // 4, 5Any — Does Any Element Match?
C# Example Code
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Check if any match the predicate
bool hasEven = numbers.Any(n => n % 2 == 0);
Console.WriteLine(hasEven); // True
bool hasNegative = numbers.Any(n => n < 0);
Console.WriteLine(hasNegative); // False
// Without predicate — checks if sequence is non-empty
var empty = new List<int>();
Console.WriteLine(numbers.Any()); // True
Console.WriteLine(empty.Any()); // FalseAll — Do All Elements Match?
C# Example Code
var numbers = new List<int> { 2, 4, 6, 8, 10 };
bool allEven = numbers.All(n => n % 2 == 0);
Console.WriteLine(allEven); // True
bool allPositive = numbers.All(n => n > 0);
Console.WriteLine(allPositive); // True
bool allLarge = numbers.All(n => n > 5);
Console.WriteLine(allLarge); // FalseContains
C# Example Code
var fruits = new List<string> { "apple", "banana", "cherry" };
Console.WriteLine(fruits.Contains("banana")); // True
Console.WriteLine(fruits.Contains("grape")); // False
// For custom objects, Contains uses Equals by default
var numbers = new List<int> { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers.Contains(3)); // TrueCount with Predicate
C# Example Code
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int evenCount = numbers.Count(n => n % 2 == 0);
Console.WriteLine(evenCount); // 5
int largeCount = numbers.Count(n => n > 7);
Console.WriteLine(largeCount); // 3Real-World Example: Finding a User by ID
C# Example Code
var users = new List<User>
{
new User(1, "Alice", "admin"),
new User(2, "Bob", "user"),
new User(3, "Charlie", "user")
};
// Safe lookup — won't throw if not found
User? found = users.FirstOrDefault(u => u.Id == 2);
if (found is not null)
Console.WriteLine($"Found: {found.Name} ({found.Role})");
// Output: Found: Bob (user)
// Check if any admins exist
bool hasAdmin = users.Any(u => u.Role == "admin");
Console.WriteLine($"Has admin: {hasAdmin}"); // True
// Are all users verified (all have a role)?
bool allHaveRole = users.All(u => !string.IsNullOrEmpty(u.Role));
Console.WriteLine($"All have role: {allHaveRole}"); // True
record User(int Id, string Name, string Role);Comparison Table
| Method | Returns | Throws when... |
|---|---|---|
First(pred) | First match | No match found |
FirstOrDefault(pred) | First match or default | Never |
Last(pred) | Last match | No match found |
LastOrDefault(pred) | Last match or default | Never |
Single(pred) | Exactly one match | 0 or 2+ matches |
SingleOrDefault(pred) | One match or default | 2+ matches |
Find(pred) (List only) | First match or default | Never |
Any(pred) | bool | Never |
All(pred) | bool | Never |
Contains(value) | bool | Never |
Count(pred) | int | Never |