18 questions foundLINQ queries collections and data sources uniformly. Most operators are lazy: the query executes when enumerated (foreach/ToList), so it reflects the latest data.
var q = nums.Where(n => n > 2); // not run yet
nums.Add(5);
foreach (var n in q) {} // runs now, sees 5
Real-world example Building a filter then materialising it only after paging parameters are known.
IEnumerable executes in memory (LINQ-to-Objects); IQueryable builds an expression tree a provider (EF Core) translates to SQL, so filtering happens in the database.
IQueryable<User> q = db.Users.Where(u => u.Active); // -> SQL WHERE
IEnumerable<User> m = list.Where(u => u.Active); // in memory
Real-world example Calling Where before ToList on a DbSet pushes the filter to the database instead of loading every row.
First returns the first match or throws if none; FirstOrDefault returns default if none; Single expects exactly one and throws otherwise.
var a = xs.First(x => x.Id==1);
var b = xs.FirstOrDefault(x => x.Id==1);
var c = xs.Single(x => x.Id==1);
Real-world example Use Single for a unique-key lookup where duplicates would indicate a data bug.
Select projects one-to-one (giving a sequence of sequences if the selector returns a collection); SelectMany flattens those into one sequence.
var tags = posts.Select(p => p.Tags); // IEnumerable<List<string>>
var flat = posts.SelectMany(p => p.Tags); // IEnumerable<string>
Real-world example Getting one flat list of all line items across all orders.
GroupBy partitions by a key into IGrouping<TKey,T>. In LINQ-to-Objects it buffers; in EF Core only certain shapes translate to SQL GROUP BY, otherwise it fails or evaluates client-side.
var byCat = products.GroupBy(p => p.Category)
.Select(g => new { g.Key, Total = g.Sum(p => p.Price) });
Real-world example Summing sales per region for a dashboard — ensure it translates to SQL for large tables.
Accessing a navigation property inside a loop triggers a query per item. Fix it by eager-loading with Include or projecting the needed data in one query.
// N+1: foreach (var o in orders) use(o.Customer.Name);
var data = db.Orders.Include(o => o.Customer).ToList();
Real-world example A report that reads each order's customer separately hammers the DB; Include batches it.
LINQ (Language Integrated Query) lets you write expressive, SQL-like queries directly in C# against collections, databases, XML, and more — you can use QUERY syntax (resembling SQL, with 'from...where...select') or METHOD syntax (chained extension methods like .Where().Select()), which are functionally equivalent and interchangeable.
// Query syntax
var adults1 = from p in people where p.Age >= 18 select p.Name;
// Method syntax (equivalent)
var adults2 = people.Where(p => p.Age >= 18).Select(p => p.Name);
Real-world example Filtering and projecting a list of Person objects down to just the names of adults, using either syntax style.
Common follow-ups: Which syntax style is generally more commonly used and recommended in modern C# codebases?
Fundamentals
Where() FILTERS a sequence, keeping only elements matching a predicate (returns the SAME element type); Select() PROJECTS/transforms each element into something new (potentially a completely different type) — they're often used together, filtering first, then transforming.
var people = new List<Person> { new("Sam", 30), new("Alex", 15) };
var adultNames = people
.Where(p => p.Age >= 18) // filters: keeps only matching Person objects
.Select(p => p.Name); // projects: transforms Person -> string
Real-world example Filtering a product catalog to in-stock items, then projecting the result down to just the product names for a dropdown list.
Common follow-ups: Can you use Select() to transform each element into a completely different, more complex object, like an anonymous type?
Generics
Most LINQ methods (Where, Select, OrderBy, etc.) build up a QUERY DEFINITION without actually executing it — the query only runs when you actually ITERATE the results (via foreach, ToList(), ToArray(), etc.), meaning the underlying data source is re-evaluated fresh EVERY time you enumerate a deferred query, reflecting any changes made since it was defined.
var query = numbers.Where(n => n > 5); // NOT executed yet, just defined
numbers.Add(10); // this NEW element IS included, since the query hasn't run yet
var results = query.ToList(); // NOW it actually executes, includes the newly added 10
Real-world example Understanding a subtle bug where a LINQ query's results unexpectedly change because the underlying collection was modified before the query was actually enumerated.
Common follow-ups: Which specific LINQ methods (like Count() or ToList()) force IMMEDIATE execution instead of deferring?
Collections
GroupBy() partitions a sequence into GROUPS based on a key selector, returning a sequence of IGrouping<TKey, TElement> objects — each group acts as both a KEY (accessible via .Key) and an ENUMERABLE of the elements sharing that key, letting you process or aggregate each group independently.
var people = new List<Person> { new("Sam", "IT"), new("Alex", "IT"), new("Jo", "HR") };
var byDept = people.GroupBy(p => p.Department);
foreach (var group in byDept) {
Console.WriteLine($"{group.Key}: {group.Count()} people");
}
// "IT: 2 people", "HR: 1 people"
Real-world example Grouping a flat list of orders by customer, or employees by department, for a summary report.
Common follow-ups: How would you combine GroupBy() with Select() to produce a summary object (count, average, etc.) for each group?
Collections