Advanced C# LINQ in 2026: Operators, Performance and Interview Questions
Master advanced LINQ operators, deferred execution, and performance optimization. Covers GroupBy, SelectMany, query optimization, and common interview questions for .NET developers.

Advanced C# LINQ operators separate experienced .NET developers from beginners. While Where and Select handle basic filtering, real-world codebases demand fluency with GroupBy, SelectMany, deferred execution patterns, and performance-aware query composition.
Interviewers often ask candidates to explain deferred vs. immediate execution, or to optimize a slow LINQ query. Understanding what happens under the hood distinguishes senior candidates from those who only memorize syntax.
Understanding Deferred Execution in LINQ
LINQ queries do not execute when declared. Execution happens when the result is enumerated, typically via foreach, ToList(), or ToArray(). This behavior, called deferred execution, enables query composition without repeated database or collection scans.
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Query is defined but not executed
var query = numbers.Where(n => n > 2);
// Adding an element after query definition
numbers.Add(6);
// Execution happens here, includes 6
foreach (var n in query)
{
Console.WriteLine(n); // Output: 3, 4, 5, 6
}The query captures the reference to numbers, not its contents. When foreach iterates, it sees the updated collection. This pattern proves useful for building dynamic queries incrementally, but causes bugs when developers expect snapshot behavior.
To force immediate execution, call ToList(), ToArray(), or ToDictionary(). These methods enumerate the source once and cache results.
GroupBy: Beyond Basic Grouping
GroupBy transforms flat collections into hierarchical structures. The operator accepts up to four parameters: key selector, element selector, result selector, and custom comparer.
public record Order(int Id, string Customer, decimal Amount, DateTime Date);
var orders = GetOrders();
// Group by customer with monthly breakdown
var customerMonthlyTotals = orders
.GroupBy(
o => new { o.Customer, Month = new DateTime(o.Date.Year, o.Date.Month, 1) },
(key, group) => new
{
key.Customer,
key.Month,
Total = group.Sum(o => o.Amount),
OrderCount = group.Count()
})
.OrderBy(x => x.Customer)
.ThenBy(x => x.Month);
// Output: Customer "Acme" spent $12,500 across 8 orders in August 2026The composite key groups by both customer and month. The result selector projects directly into an analytics-ready shape, avoiding a second Select pass.
GroupBy must buffer the entire source to form groups. For large datasets or streaming scenarios, consider database-side grouping with EF Core, or use ToLookup when the grouped result will be accessed multiple times.
SelectMany: Flattening Nested Collections
SelectMany flattens one-to-many relationships into a single sequence. The operator handles scenarios where each element produces zero or more results.
public record Category(string Name, List<Product> Products);
public record Product(string Name, decimal Price);
var categories = GetCategories();
// Flatten all products with their category names
var allProducts = categories
.SelectMany(
category => category.Products,
(category, product) => new
{
CategoryName = category.Name,
ProductName = product.Name,
product.Price
});
// Filter after flattening
var expensiveProducts = allProducts
.Where(p => p.Price > 100)
.OrderByDescending(p => p.Price);The two-parameter overload of SelectMany provides access to both the parent and child elements, enabling projections that combine data from both levels. This pattern replaces nested foreach loops with a declarative approach.
Query syntax offers an alternative that some developers find more readable for complex joins:
var productsWithCategories =
from category in categories
from product in category.Products
where product.Price > 100
select new { category.Name, product };Both compile to the same SelectMany call. Choose based on team convention and query complexity.
Optimizing LINQ Performance
LINQ abstractions carry overhead. Each chained operator creates an iterator object, and lambda expressions add delegate invocation costs. For hot paths processing thousands of elements per second, these costs compound.
Avoiding Multiple Enumerations
IEnumerable<Order> GetExpensiveOrders(IEnumerable<Order> orders)
{
var filtered = orders.Where(o => o.Amount > 1000);
// WARNING: Enumerates twice if orders is not materialized
if (!filtered.Any())
return Enumerable.Empty<Order>();
return filtered.OrderBy(o => o.Date);
}If orders comes from a database query or file stream, this code executes the source twice. Materialize with ToList() before multiple operations:
IEnumerable<Order> GetExpensiveOrders(IEnumerable<Order> orders)
{
var filtered = orders.Where(o => o.Amount > 1000).ToList();
if (filtered.Count == 0)
return Enumerable.Empty<Order>();
return filtered.OrderBy(o => o.Date);
}Choosing the Right Operator
Some LINQ operators have optimized implementations for specific collection types:
| Operation | Slow Version | Fast Version | Notes |
|---|---|---|---|
| Count | collection.Count() | collection.Count | Use property for ICollection<T> |
| Element access | collection.ElementAt(5) | collection[5] | Use indexer for IList<T> |
| Existence check | collection.Count() > 0 | collection.Any() | Any() short-circuits |
| First match | collection.Where(x).First() | collection.First(x) | Single pass vs. two iterators |
Ready to ace your .NET interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Aggregate: Custom Reduction Operations
Aggregate builds a single result from a sequence by applying an accumulator function. While Sum, Max, and Average handle common cases, Aggregate enables custom reductions.
var transactions = new List<Transaction>
{
new("Deposit", 1000),
new("Withdrawal", -200),
new("Deposit", 500),
new("Fee", -25)
};
// Running balance with audit trail
var balanceHistory = transactions.Aggregate(
new List<(string Op, decimal Balance)>(),
(history, tx) =>
{
var previousBalance = history.Count > 0 ? history[^1].Balance : 0;
history.Add((tx.Type, previousBalance + tx.Amount));
return history;
});
// Output: [(Deposit, 1000), (Withdrawal, 800), (Deposit, 1300), (Fee, 1275)]The three-parameter overload adds a seed value and final result selector, useful when the accumulator type differs from the result type.
LINQ to Objects vs. LINQ to Entities
Understanding where a query executes determines its performance characteristics. LINQ to Objects runs in-memory with C# delegates. LINQ to Entities (EF Core) translates expressions into SQL.
using var context = new AppDbContext();
// BAD: Loads all orders, filters in memory
var badQuery = context.Orders
.ToList()
.Where(o => o.Amount > 1000);
// GOOD: Filters in database, loads only matching rows
var goodQuery = context.Orders
.Where(o => o.Amount > 1000)
.ToList();The placement of ToList() changes everything. In the bad example, SQL Server returns thousands of rows, then C# discards most of them. In the good example, the WHERE clause runs server-side.
For complex projections, EF Core 9 (current as of August 2026) supports split queries to avoid cartesian explosion when loading related collections.
Common LINQ Interview Questions
Technical interviews frequently probe LINQ understanding through conceptual questions and live coding. The following scenarios appear regularly in .NET developer interviews.
"What is the difference between IEnumerable and IQueryable?"
IEnumerable<T> works with in-memory delegates. Each chained operator creates an iterator that processes elements one by one. The runtime cannot optimize across operators.
IQueryable<T> works with expression trees. The entire query compiles into a data structure that a provider (EF Core, for example) translates into SQL. This enables server-side filtering, sorting, and projection.
// Expression tree, translates to SQL WHERE clause
IQueryable<Order> dbQuery = context.Orders.Where(o => o.Amount > 1000);
// Delegate, executes in C# memory
IEnumerable<Order> memoryQuery = orders.Where(o => o.Amount > 1000);Choose IQueryable when querying external data sources. Choose IEnumerable for in-memory collections or when the query must use C#-only features like regex or custom methods.
"How do you find duplicates in a collection?"
This coding question tests GroupBy and filtering:
var emails = new[] { "a@test.com", "b@test.com", "a@test.com", "c@test.com" };
var duplicates = emails
.GroupBy(e => e, StringComparer.OrdinalIgnoreCase)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
// Output: ["a@test.com"]The solution groups by the element itself, filters groups with more than one member, and projects the key. The StringComparer.OrdinalIgnoreCase parameter handles case-insensitive email matching.
"How do you implement pagination with LINQ?"
public IEnumerable<Product> GetProductsPage(int pageNumber, int pageSize)
{
return context.Products
.OrderBy(p => p.Id)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
}The OrderBy clause is mandatory for deterministic pagination. Without it, database engines may return rows in arbitrary order, causing items to appear on multiple pages or disappear entirely.
For more C# interview questions, SharpSkill covers advanced features including pattern matching, records, and nullable reference types.
Writing Maintainable LINQ Queries
Query readability degrades quickly when chains exceed five or six operators. Extract intermediate results into named variables, or break complex logic into separate methods.
public IEnumerable<CustomerReport> GenerateReports(IEnumerable<Order> orders)
{
var ordersByCustomer = GroupOrdersByCustomer(orders);
var reportsWithMetrics = CalculateCustomerMetrics(ordersByCustomer);
return ApplyBusinessRules(reportsWithMetrics);
}
private IEnumerable<IGrouping<string, Order>> GroupOrdersByCustomer(
IEnumerable<Order> orders)
{
return orders
.Where(o => o.Status == OrderStatus.Completed)
.GroupBy(o => o.CustomerId);
}
private IEnumerable<CustomerReport> CalculateCustomerMetrics(
IEnumerable<IGrouping<string, Order>> groups)
{
return groups.Select(g => new CustomerReport
{
CustomerId = g.Key,
TotalSpent = g.Sum(o => o.Amount),
OrderCount = g.Count(),
AverageOrderValue = g.Average(o => o.Amount)
});
}This structure makes each transformation testable in isolation. The method names document intent better than inline comments.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Key Takeaways for .NET Developers
- Deferred execution delays query evaluation until enumeration. Use
ToList()orToArray()to materialize results when snapshot behavior is needed or when multiple enumerations would hit a data source repeatedly. GroupBybuffers the entire source. For large datasets, push grouping to the database with EF Core rather than pulling rows into memory.SelectManyflattens nested collections. The two-parameter overload provides access to both parent and child elements for combined projections.IQueryablebuilds expression trees for provider translation.IEnumerableexecutes delegates in-memory. Choosing the wrong interface leads to full table scans.- Operator placement matters for EF Core performance. Place
Where,OrderBy, andTakebeforeToList()to run filters server-side. - Interview questions often test deferred execution understanding, duplicate detection with
GroupBy, and theIEnumerablevs.IQueryabledistinction.
Can you spot the bug in .NET?
One real snippet, one hidden bug, one attempt a day. No account needed to try.

Written by
Anthony Fillion-MailletFounder of SharpSkill
Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.
Updated on August 23, 2026
Tags
Share
Related articles

Clean Code Architecture C#: Complete Guide and Interview Questions 2026
Master Clean Code and Clean Architecture in C# with SOLID principles, practical patterns, and interview questions. Learn layered architecture, dependency injection, and testable code design for .NET 10.

Top 25 ASP.NET Core Interview Questions: Middleware, DI and Minimal APIs
Master the most common ASP.NET Core interview questions on middleware pipelines, dependency injection lifetimes, and minimal APIs. Covers .NET 9 and .NET 10 features with code examples.

Entity Framework Core: Performance Optimization and Best Practices in 2026
Master EF Core 10 performance optimization with AsNoTracking, compiled queries, split queries, batch operations, and the new LeftJoin operator. Practical C# examples for production .NET 10 applications.