Using SingleOrDefault in C# LINQ
c# linq singleordefault: Learn how SingleOrDefault works in C# LINQ, what it returns on empty or duplicate matches, when it throws, and how it compares to FirstOrDefault.
When you query a collection and expect either no match or exactly one match, c# linq singleordefault gives you a way to encode that expectation directly. It returns the single matching element, returns the type's default value when nothing matches, and throws an InvalidOperationException when more than one element matches.
What SingleOrDefault Actually Returns
SingleOrDefault is an extension method defined in the System.Linq namespace. It evaluates the entire sequence and applies a predicate when one is provided. The method has three possible outcomes:
- If exactly one element matches, that element is returned.
- If no element matches, the default value for the element type is returned.
- If more than one element matches, an
InvalidOperationExceptionis thrown.
The default value depends on the element type. For reference types and nullable value types, the default is null. For non-nullable value types such as int, bool, or DateTime, the default is the zero-initialized value: 0, false, or DateTime.MinValue respectively.
Basic Syntax and Minimal Example
The simplest form of the method takes no arguments and returns the only element of the sequence:
var items = new List<string> { "alpha" }; string only = items.SingleOrDefault(); Console.WriteLine(only); // alpha
The predicate form filters the sequence first:
var users = new List<User> { new User { Id = 1, Email = "alice@example.com" }, new User { Id = 2, Email = "bob@example.com" } }; User match = users.SingleOrDefault(u => u.Email == "alice@example.com");
When the predicate matches exactly one user, match holds that User instance. When no user matches, match is null because User is a reference type. This makes the predicate form useful for lookups where the key is expected to be unique, such as an email address or an order number.
The Three Possible Outcomes in Practice
The behavior differs meaningfully between an empty result and a duplicate result. Consider this sequence:
var orders = new List<Order> { new Order { Number = "A-100", CustomerId = 7 }, new Order { Number = "B-200", CustomerId = 7 } }; Order duplicate = orders.SingleOrDefault(o => o.CustomerId == 7);
Here two orders belong to customer 7, so the call throws InvalidOperationException rather than returning a value. That exception is the method's way of telling you that your assumption about the data is wrong: you expected at most one match, but the data violates that expectation.
An empty match behaves differently:
Order missing = orders.SingleOrDefault(o => o.CustomerId == 99); // missing is null
The method cannot distinguish between "no match" and "the matched element happened to be null" when the element type is a reference type. If your data model allows null values inside the collection, you cannot tell those two cases apart without additional checks.
SingleOrDefault vs FirstOrDefault vs Single
The three methods are easy to confuse because their names are similar, but their contracts differ in ways that matter at runtime.
| Method | Empty sequence | One match | Multiple matches |
|---|---|---|---|
SingleOrDefault | Returns default | Returns the element | Throws |
FirstOrDefault | Returns default | Returns the element | Returns first match |
Single | Throws | Returns the element | Throws |
FirstOrDefault stops enumerating as soon as it finds the first match. SingleOrDefault cannot stop early because it must verify that no second match exists. That difference is not just about behavior; it also affects runtime cost.
Use SingleOrDefault when the data is expected to be unique by constraint, such as a primary key lookup or a unique business identifier. Use FirstOrDefault when multiple matches are acceptable and you only care about the first one, such as the most recent log entry for a user.
When SingleOrDefault Throws InvalidOperationException
The exception is thrown in two situations: when the sequence contains more than one matching element, and when the sequence itself contains more than one element in the parameterless form. The parameterless form is rarely useful in practice because it requires the entire collection to have exactly one element.
A common source of unexpected exceptions is a predicate that is not selective enough. For example:
var active = users.SingleOrDefault(u => u.IsActive);
If two users are marked active, this throws. The fix is either to tighten the predicate so it targets a unique value, or to switch to FirstOrDefault when the first active user is acceptable.
Another source is a collection that appears unique in tests but contains duplicates in production because of data imported from an external system. In that situation, the exception is useful because it surfaces a data integrity problem early rather than silently picking one of several rows.
Performance and Runtime Cost
SingleOrDefault must enumerate the entire sequence to confirm that no second match exists. This is the main runtime difference from FirstOrDefault, which stops at the first match. For small in-memory collections the difference is negligible, but for large sequences or sequences backed by remote data sources, the cost can be significant.
When the source is an IEnumerable<T> backed by a database query, the entire result set may need to be materialized before the method can verify uniqueness. If you know the underlying data has a unique constraint, you can often replace SingleOrDefault with FirstOrDefault to avoid scanning the full result set, but only when the contract allows it. If uniqueness is a requirement of your logic, removing the uniqueness check changes the behavior of the code.
There is no built-in way to make SingleOrDefault stop early. If you need both the uniqueness check and early termination, you would have to write a custom loop that tracks whether a second match exists:
static T? SingleOrDefaultFast<T>(IEnumerable<T> source, Func<T, bool> predicate) { T? found = default; bool hasMatch = false; foreach (var item in source) { if (!predicate(item)) continue; if (hasMatch) { throw new InvalidOperationException("More than one match found."); } found = item; hasMatch = true; } return hasMatch ? found : default; }
This custom loop behaves identically to the built-in method in terms of outcomes, but it does not change the fact that the entire sequence must be scanned to detect a duplicate. The only way to avoid the full scan is to rely on an index or a unique constraint at the data source.
Null Handling and Default Value Pitfalls
When the element type is a reference type, SingleOrDefault returns null for an empty match. This creates an ambiguity when the collection itself can contain null values. Consider:
var entries = new List<string?> { null, "alpha" }; string? result = entries.SingleOrDefault(e => e == null);
Here the predicate matches exactly one element, which is null, so the method returns null. The caller cannot tell whether this means "no match" or "the match was null". If that distinction matters, you need to check the count separately or use a different approach, such as filtering with Where and then inspecting the result.
For value types, the default value is also a valid value. For example, int has a default of 0, so a call that returns 0 could mean either "no match" or "the matched element was 0". When 0 is a meaningful value in your domain, use a nullable type or check the count explicitly.
Practical Usage Patterns
The most common practical use is a lookup where the key is unique by design:
var account = accounts.SingleOrDefault(a => a.AccountNumber == input); if (account is null) { return NotFound(); } return Ok(account);
This pattern is clear and expresses the uniqueness expectation in the query itself. Another common use is configuration lookup where a default is acceptable:
var timeout = settings.SingleOrDefault(s => s.Key == "Timeout")?.Value ?? "30";
The null-conditional operator combined with the null-coalescing operator handles the empty case cleanly.
A less obvious use is validation: the exception thrown by SingleOrDefault can serve as an assertion that a collection has exactly one element. However, relying on exceptions for control flow is generally discouraged. If you expect the collection to contain exactly one element and the empty case is an error, prefer Single so the exception message and behavior are explicit.
Maintainability and Readability Considerations
SingleOrDefault communicates intent better than a manual loop. A reader sees the method name and immediately understands that the code expects zero or one match. That implicit documentation is valuable in codebases where data uniqueness is not obvious.
The flip side is that the method hides the exception path. A developer who does not know the method's contract may be surprised when it throws on duplicate data. Documenting the assumption in a comment, or better, by naming the variable clearly, reduces that surprise:
// Expects at most one active subscription per account. var subscription = subscriptions.SingleOrDefault(s => s.AccountId == id && s.IsActive);
When the data source is a database, the uniqueness assumption should match a real constraint in the schema. If the schema does not enforce uniqueness, the LINQ call may work in development and fail in production when duplicate rows appear. Aligning the code's assumption with the database constraint prevents that class of failure.