LINQ (Language Integrated Query) is one of the most powerful features in C#. It simplifies data querying with a clean, readable syntax and works seamlessly with collections, databases, XML, and more. However, while LINQ improves developer productivity, it can also introduce hidden performance issues when used incorrectly.
Many developers assume LINQ is always optimized by the runtime, but that's not always true. Poor query composition, unnecessary enumerations, excessive allocations, and inefficient projections can negatively impact application performance, especially in high-traffic APIs and data-intensive applications.
In this article, we'll explore the most common LINQ performance pitfalls and practical techniques to write efficient, production-ready code.
Understand Deferred Execution
One of LINQ's core features is deferred execution. Most LINQ queries are not executed when they're defined—they execute only when the data is enumerated.
var expensiveProducts = products
.Where(p => p.Price > 1000);
At this point, no filtering has occurred. The query executes only when it's iterated.
foreach (var product in expensiveProducts)
{
Console.WriteLine(product.Name);
}
Deferred execution is useful because it avoids unnecessary work, but it can also produce unexpected behavior if the underlying collection changes before enumeration.
Avoid Multiple Enumeration
A common performance mistake is enumerating the same LINQ query multiple times.
Instead of:
var activeUsers = users.Where(u => u.IsActive);
var count = activeUsers.Count();
foreach (var user in activeUsers)
{
Console.WriteLine(user.Name);
}
The filtering operation executes twice.
A better approach is:
var activeUsers = users
.Where(u => u.IsActive)
.ToList();
var count = activeUsers.Count;
foreach (var user in activeUsers)
{
Console.WriteLine(user.Name);
}
Materializing the query once avoids repeated execution and improves performance.
Choose Any() Instead of Count()
When checking whether data exists, many developers use:
if (products.Count() > 0)
{
// Process data
}
A more efficient alternative is:
if (products.Any())
{
// Process data
}
Any() stops after finding the first matching element, while Count() may iterate through the entire collection, depending on the underlying source.
Filter Before Projecting
The order of LINQ operations affects performance.
Less efficient:
var result = products
.Select(p => new
{
p.Name,
p.Price
})
.Where(p => p.Price > 1000);
More efficient:
var result = products
.Where(p => p.Price > 1000)
.Select(p => new
{
p.Name,
p.Price
});
Filtering first reduces the number of objects created during projection.
Avoid Unnecessary ToList()
Calling ToList() too early forces immediate execution and allocates additional memory.
Avoid:
var result = products
.ToList()
.Where(p => p.Price > 500);
Instead:
var result = products
.Where(p => p.Price > 500)
.ToList();
Execute the query only after all filtering and transformations are complete.
Prefer Projections Over Entire Objects
When working with Entity Framework Core, retrieving complete entities when only a few fields are required increases network traffic and memory usage.
Instead of:

Join the conversation! Your thoughts help the community grow.