Skip to main content
Article complete

Get one like this every Tuesday at 7 PM IST.

codewithmukesh
Back to blog
dotnet 27 min read New

30 LINQ Interview Questions That Actually Get Asked in 2026

30 real LINQ interview questions with great answers, red flags, and follow-ups. Deferred execution, IQueryable, expression trees, and the .NET 9/10 operators.

30 real LINQ interview questions with great answers, red flags, and follow-ups. Deferred execution, IQueryable, expression trees, and the .NET 9/10 operators.

dotnet

interview-questions linq csharp csharp-14 dotnet-10 interview-prep dotnet-interview linq-interview deferred-execution iqueryable ienumerable expression-trees ef-core query-syntax method-syntax selectmany groupby countby performance

Mukesh Murugan
Mukesh Murugan
Software Engineer

LINQ interview questions in 2026 are rarely “what does Select do?” anymore. They’re scenario questions: your query hits the database twice when you only meant to run it once, your .Where() throws “could not be translated”, or a filter you built inside a loop returns the wrong rows. Those reveal whether you actually understand what LINQ compiles into, or just chain methods until the result looks right.

I’ve interviewed plenty of .NET developers, and LINQ is one of the fastest ways to tell a confident candidate from a shaky one. Everyone can write list.Where(x => x.IsActive). Far fewer can explain when that query runs, why IQueryable and IEnumerable behave so differently, or what happens when you enumerate the same query twice. This article is 30 LINQ interview questions in the format that actually gets used: a real scenario, how I’d answer it, the answer that gets you rejected, and the follow-up the interviewer chains next.

Everything is current for .NET 10 and C# 14, including the LINQ methods added in .NET 9 (CountBy, AggregateBy, Index) and the native LeftJoin/RightJoin operators added in .NET 10. Let’s get into it.

What Makes a Good LINQ Interview Answer?

A strong LINQ answer does two things: it explains when the query executes and where it runs (in memory or in the database), and it names the trade-off. LINQ looks simple on the surface, so interviewers use it to find out whether you understand the machinery underneath - deferred execution, expression trees, and the IEnumerable/IQueryable split.

The 30 questions below are grouped into 6 categories that match how a LINQ round actually flows. Jump to the one you want to sharpen:

This page is part of my .NET interview prep series. For the broader set, see the .NET interview questions hub, the EF Core interview questions for the data-access layer, and the .NET Web API interview questions for the API that sits on top.


LINQ Fundamentals

These come up in the screening round. They look basic, but the good answers show you understand the mechanism, not just the syntax.

Q1. What Is LINQ and What Problem Does It Solve?

Junior

LINQ (Language Integrated Query) is a uniform way to query collections, databases, XML, and other data sources directly in C# with the same set of operators. Before LINQ, you wrote foreach loops for in-memory data, raw SQL strings for a database, and XPath for XML - three different mental models. LINQ gives you one declarative syntax (Where, Select, GroupBy, OrderBy) that works across all of them, with compile-time type checking and IntelliSense.

The part I make sure to add: LINQ is declarative. You describe what you want, not how to loop and accumulate it, and the provider decides how to execute it - in memory for objects, as SQL for EF Core.

Red flag answer: “LINQ is a way to write SQL inside C#.” - That’s only LINQ to Entities; it misses that LINQ works over any sequence, most of which never touch a database.

Q2. Is There a Real Difference Between Query Syntax and Method Syntax?

Junior

No - they compile to the same thing. Query syntax (from x in source where ... select ...) is syntactic sugar that the C# compiler translates into method calls (source.Where(...).Select(...)). They’re functionally identical, and you can mix them.

// Query syntax
var a = from p in products where p.Price > 100 select p.Name;
// Method syntax - what the compiler turns the above into
var b = products.Where(p => p.Price > 100).Select(p => p.Name);

I use method syntax most of the time because it chains naturally and exposes operators that have no query keyword (Count, Any, First, Skip/Take). I reach for query syntax when a query has multiple joins or let clauses, where it reads more clearly.

Red flag answer: “Query syntax is faster” or “method syntax is more powerful at runtime.” - They produce the same compiled code; the only difference is readability.

Q3. What Are the Main LINQ Providers?

Junior

A LINQ provider is what actually executes the query against a particular source. The ones worth naming:

  1. LINQ to Objects - runs over any in-memory IEnumerable<T> (lists, arrays, dictionaries). Operators run as compiled C# delegates.
  2. LINQ to Entities (EF Core) - translates the query into SQL and runs it in the database.
  3. LINQ to XML - queries XDocument/XElement trees in System.Xml.Linq.
  4. PLINQ - Parallel LINQ, via .AsParallel(), spreads in-memory work across threads.

The distinction that matters: LINQ to Objects executes delegates in memory, while LINQ to Entities builds an expression tree that a provider turns into SQL. (LINQ to SQL, the old System.Data.Linq, still exists but is legacy - I wouldn’t build on it today.)

Red flag answer: “They’re all the same, LINQ just runs the query.” - The execution model is completely different between in-memory and database providers, and that difference is the whole reason the next several questions exist.

Q4. How Do Extension Methods and Lambdas Make LINQ Work?

Mid

LINQ operators like Where and Select aren’t built into the language - they’re extension methods defined on IEnumerable<T> (in System.Linq.Enumerable) and IQueryable<T> (in System.Linq.Queryable). That’s why a using System.Linq; is what makes them appear on a list. Each one takes a lambda that describes the per-element logic, like p => p.Price > 100.

The detail interviewers probe is what that lambda becomes. For LINQ to Objects, the lambda compiles to a Func<> delegate that runs directly. For IQueryable, the same-looking lambda compiles to an Expression<Func<>> - a data structure the provider inspects and translates. Same syntax, very different runtime behavior, which is Q11 and Q13.

Red flag answer: “Lambdas are just shorter anonymous methods.” - True for delegates, but it misses that on IQueryable the lambda becomes an expression tree, not executable code.

Q5. What’s the Difference Between Select and SelectMany?

Mid

Select projects each element into one result - it’s a one-to-one transform. SelectMany projects each element into a sequence and then flattens all those sequences into one - it’s one-to-many flattened.

var orders = customers.Select(c => c.Orders); // IEnumerable<List<Order>> - nested
var orders = customers.SelectMany(c => c.Orders); // IEnumerable<Order> - flat

If each customer has a list of orders and you want a single flat list of all orders, Select leaves you with a list of lists you’d have to flatten yourself; SelectMany does it in one step. In query syntax, a second from clause is SelectMany under the hood.

Red flag answer: “They’re basically interchangeable.” - Use Select where you need SelectMany and you end up with IEnumerable<List<T>> and a confusing second loop.


Execution Model

This is the heart of LINQ and the category that separates people who’ve debugged a query from people who’ve only written one.

Q6. When Does a LINQ Query Actually Execute, and How Is That Deferral Implemented?

Mid

Most LINQ operators use deferred execution: building a query with Where, Select, or OrderBy doesn’t run anything - it composes a description of the work. The query runs only when you enumerate it, by calling ToList, ToArray, Count, First, or using foreach.

How it’s implemented matters for the follow-up. For LINQ to Objects, operators are iterator methods using yield return - calling Where just returns an iterator object; the predicate runs lazily as you pull each element. For IQueryable, deferral works differently: the operators build up an expression tree, and nothing is sent to the database until you enumerate.

Red flag answer: “The query runs as soon as I write the Where.” - Then you can’t explain why a query you “ran” earlier suddenly hits the database inside a later foreach.

Follow-up: “Name two operators that force immediate execution and two that don’t.”

Q7. Which Operators Force Execution and Which Stay Lazy?

Mid

The rule of thumb: operators that return another sequence are lazy (deferred), and operators that return a single value or a materialized collection are immediate (they run the query right away).

  1. Deferred (return IEnumerable/IQueryable): Where, Select, SelectMany, OrderBy, Take, Skip, Distinct, GroupBy, Cast.
  2. Immediate (return a value or a new collection): ToList, ToArray, ToDictionary, Count, Sum, First/FirstOrDefault, Single, Any, All, Max.

The mental test I share: if the return type is still a sequence, nothing has executed yet. The moment you ask for a value or a concrete collection, the whole chain runs.

Red flag answer:OrderBy runs immediately because it has to sort.” - It’s still deferred; the sort happens when you enumerate, not when you call OrderBy.

Q8. What’s the Difference Between ToList, ToArray, and AsEnumerable?

Junior

ToList() and ToArray() both execute the query immediately and copy the results into memory - a List<T> or a T[]. I default to ToList() because it’s flexible (add/remove), and reach for ToArray() when the result is fixed-size or I want the slightly smaller memory footprint.

AsEnumerable() is different - it doesn’t execute anything. It just changes the compile-time type from IQueryable<T> to IEnumerable<T>, which forces every operator after it to run in memory as LINQ to Objects instead of being translated to SQL. That’s a tool with a sharp edge, covered in Q14.

Red flag answer:AsEnumerable runs the query like ToList.” - It doesn’t materialize anything; it only flips the boundary between database and in-memory evaluation.

Q9. You Pass an IEnumerable Around and Notice the Database Is Queried Twice. What Happened?

Senior

That’s multiple enumeration. Because the query is deferred, the IEnumerable you’re holding is a recipe, not a result. Every time something enumerates it - a foreach, a .Count(), then a .Any() - the entire query re-runs from scratch, hitting the database again each time.

IEnumerable<Order> orders = db.Orders.Where(o => o.IsOpen); // nothing ran yet
if (orders.Any()) // query #1
Process(orders.Count()); // query #2
foreach (var o in orders) { ... } // query #3

The fix is to materialize once with ToList() and work with the list. This is exactly why analyzers raise Possible multiple enumeration of IEnumerable - it’s a real bug, not a style nit.

Red flag answer: “It can’t query twice, I only wrote the Where once.” - You wrote the query once, but you enumerated it three times.

Follow-up: “When would you deliberately keep it deferred instead of materializing?”

Q10. What Does This Loop Print, and Why?

Senior

A classic deferred-execution trap. You build a query inside a loop and capture the loop variable:

var queries = new List<IEnumerable<int>>();
var numbers = new[] { 1, 2, 3 };
for (int i = 0; i < 3; i++)
queries.Add(numbers.Where(n => n > i)); // 'i' captured by reference
foreach (var q in queries)
Console.WriteLine(q.Count()); // prints 0, 0, 0 - not 2, 1, 0

Each lambda captures the variable i, not its value at the time. Because execution is deferred to the Count() calls after the loop, by then i is 3, so every query filters n > 3 and returns 0. The fix is to copy i into a loop-local variable (int local = i;) so each closure captures its own value. Note a foreach variable is per-iteration since C# 5, but a for counter is not - this still bites.

Red flag answer: “It prints 2, 1, 0.” - That assumes eager evaluation; deferral is the entire point of the trap.


IEnumerable vs IQueryable

This category tests whether you understand that the same LINQ code can run two completely different ways depending on the static type.

Q11. What’s the Real Difference Between IEnumerable and IQueryable?

Mid

IEnumerable<T> represents an in-memory sequence; its LINQ operators take Func<> delegates and run as compiled C# in your process. IQueryable<T> represents a query against an external source; its operators take Expression<Func<>> and build an expression tree that a provider (EF Core) translates - usually to SQL - and runs in the database.

The practical consequence is where the filtering happens:

// IQueryable: WHERE IsActive = 1 runs in SQL, returns only matching rows
var good = await db.Products.Where(p => p.IsActive).ToListAsync();
// IEnumerable: every row is pulled into memory FIRST, then filtered in C#
var bad = db.Products.AsEnumerable().Where(p => p.IsActive).ToList();

Keeping a query as IQueryable until the last moment lets filters, paging, and projections push down to the database. The moment it becomes IEnumerable, everything after runs locally.

Red flag answer: “They’re both just collections you can loop over.” - That misunderstanding is how full-table loads end up in production.

Q12. What Is an Expression Tree, and How Does EF Core Use It?

Senior

An expression tree is a data structure that represents code as data - a tree of nodes describing an operation (a binary comparison, a member access, a constant) rather than compiled, runnable instructions. When you write p => p.Price > 100 against an IQueryable, the compiler doesn’t produce a delegate; it produces an Expression<Func<Product, bool>> - an object EF Core can walk.

EF Core’s query provider visits that tree, recognizes “member access Price, greater-than, constant 100”, and emits the matching SQL WHERE Price > 100. Because it’s data, EF Core can inspect and translate it; a compiled Func<> would be an opaque black box it couldn’t read. That’s the entire reason IQueryable can become SQL and IEnumerable can’t.

Red flag answer: “An expression tree is just a lambda.” - A lambda can compile to either a delegate or an expression tree; only the expression-tree form is inspectable, and that’s the point.

Q13. What’s the Difference Between Func and Expression of Func, and Why Does EF Core Need the Latter?

Senior

Func<Product, bool> is a compiled delegate - executable code you can invoke but not inspect. Expression<Func<Product, bool>> is that same lambda represented as an expression tree - data you can analyze but not directly invoke (you have to .Compile() it first).

Func<Product, bool> predicate = p => p.Price > 100; // runnable, opaque
Expression<Func<Product, bool>> expr = p => p.Price > 100; // inspectable tree

EF Core’s IQueryable.Where takes the Expression form precisely because it needs to read the logic to translate it to SQL. If EF accepted a plain Func, it would have nothing to translate and would be forced to pull all rows into memory and run the delegate there. This is why passing a Func where EF expects an Expression silently drops you to client-side evaluation.

Red flag answer: “They’re the same, one’s just longer.” - One is executable and one is translatable; conflating them is how queries quietly stop running in the database.

Q14. AsEnumerable vs AsQueryable - Where Does the Client/Server Boundary Flip?

Senior

AsEnumerable() casts an IQueryable down to IEnumerable, so every operator after it runs in memory - it moves the boundary earlier and pulls data client-side. AsQueryable() casts an IEnumerable up to IQueryable, usually to satisfy an API that expects IQueryable or to build a query dynamically; over in-memory data it still executes in memory.

The interview point is using AsEnumerable() deliberately. When part of a query can’t translate to SQL (a C# method, a custom format), you let the database do the heavy filtering first, then call AsEnumerable() to finish the un-translatable part locally on the already-reduced set:

var rows = db.Orders
.Where(o => o.Total > 1000) // runs in SQL - narrows the set
.AsEnumerable() // boundary: everything below runs in memory
.Where(o => IsBusinessDay(o.CreatedAt)) // C# method, no SQL
.ToList(); // sync ToList - no async after AsEnumerable

Red flag answer:AsEnumerable makes the query faster.” - It can make it far slower by moving work out of the database; it’s about where code runs, not speed.

Q15. Your .Where() Throws “Could Not Be Translated”. What Happened and How Do You Fix It?

Senior

EF Core tried to translate your LINQ into SQL and hit something with no SQL equivalent - usually a custom C# method or a .NET API call inside the predicate:

// Throws: FormatName is a C# method EF Core can't turn into SQL
var rows = await db.Customers
.Where(c => FormatName(c.Name) == input)
.ToListAsync();

Since EF Core 3.0 the framework refuses to silently run this on the client (which used to cause invisible full scans). My fixes, in order: rewrite the predicate so it compares raw columns EF can translate; move the un-translatable part after an explicit AsEnumerable() once the set is already small; or push the logic into a computed column or raw SQL. The one thing I never do is slap AsEnumerable() at the very start to make the error vanish.

Red flag answer: “I’d add .AsEnumerable() before the .Where() so it stops throwing.” - That fixes the exception by loading the whole table into memory. Wrong trade.


Operators in Depth

Expect at least a couple of these. The interviewer wants to see you know the edge cases, not just the happy path.

Q16. What’s the Difference Between First, FirstOrDefault, Single, and SingleOrDefault?

Junior

They differ on two axes: how many matches they expect, and what they do when there are none.

  1. First - returns the first match; throws if the sequence is empty.
  2. FirstOrDefault - returns the first match, or default (null for reference types) if empty. No throw.
  3. Single - asserts there is exactly one match; throws if there are zero or more than one.
  4. SingleOrDefault - returns the one match or default if none, but still throws if there’s more than one.

I use First/FirstOrDefault when “first of possibly many” is fine, and Single/SingleOrDefault when more than one match means a bug - looking up by a unique key, for example. Single makes that assumption explicit and fails loud.

Red flag answer: “They all do the same thing, I just use First everywhere.” - Then you’ve thrown away the duplicate-detection guarantee Single exists to give you.

Q17. How Do You Group With GroupBy, Including a Composite Key?

Mid

GroupBy partitions a sequence into groups keyed by a selector. Each group is an IGrouping<TKey, TElement> - it has a Key and is itself enumerable.

// Single key: total revenue per category
var byCategory = products
.GroupBy(p => p.Category)
.Select(g => new { g.Key, Total = g.Sum(p => p.Price) });
// Composite key: group by category AND year using an anonymous type
var byCategoryYear = products
.GroupBy(p => new { p.Category, Year = p.CreatedAt.Year })
.Select(g => new { g.Key.Category, g.Key.Year, Count = g.Count() });

An anonymous type as the key gives you a composite group because anonymous types have value-based equality built in. The thing to flag for EF Core: a GroupBy followed by an aggregate translates to SQL GROUP BY, but a GroupBy that returns the actual grouped elements often can’t translate and falls back to client evaluation.

Red flag answer: “You can only group by a single property.” - An anonymous (or tuple) key groups by as many properties as you want.

Q18. How Do You Write Inner and Left Joins in LINQ?

Mid

An inner join keeps only rows that match on both sides, via the join keyword or the Join operator:

var matched =
from o in orders
join c in customers on o.CustomerId equals c.Id
select new { o.Id, c.Name };

A left join - keep every order even when no customer matches - traditionally needs GroupJoin plus SelectMany with DefaultIfEmpty:

var leftJoined =
from o in orders
join c in customers on o.CustomerId equals c.Id into grp
from c in grp.DefaultIfEmpty()
select new { o.Id, CustomerName = c == null ? "(none)" : c.Name };

The DefaultIfEmpty() is what turns the inner join into a left join - it supplies a null on the right side when there’s no match. Most of the time in EF Core, if there’s a navigation property, I’d just project through it and let EF generate the join.

Red flag answer: “LINQ can’t do left joins.” - It can; the classic shape is GroupJoin + DefaultIfEmpty, and .NET 10 adds a dedicated operator (next question).

Q19. What Changed About Joins in .NET 10?

Mid

.NET 10 added native LeftJoin and RightJoin operators to LINQ, in both System.Linq.Enumerable and System.Linq.Queryable. They replace the awkward GroupJoin + SelectMany + DefaultIfEmpty dance with a single, readable call:

// .NET 10: native left join - the unmatched right side is nullable
var leftJoined = orders.LeftJoin(
customers,
o => o.CustomerId, // outer key
c => c.Id, // inner key
(o, c) => new { o.Id, CustomerName = c == null ? "(none)" : c.Name });

In the result selector the inner element is nullable (Func<TOuter, TInner?, TResult>), which is exactly the “no match” case. EF Core 10 translates these operators to SQL LEFT JOIN/RIGHT JOIN, so they work over the database too. The caveat worth mentioning: they’re operators only - there’s no leftjoin query-syntax keyword, so you call them in method syntax.

Red flag answer: “Joins are the same as they’ve always been.” - Knowing the .NET 10 operators is a quick signal you keep up with the platform.

Q20. How Does Aggregate Work, and When Would You Use It?

Mid

Aggregate folds a sequence into a single value by threading an accumulator through every element. The overload I reach for takes a seed and an accumulator function:

// Seed of 0, add each number's length - total characters across all words
var totalChars = words.Aggregate(0, (sum, w) => sum + w.Length);
// Build a comma-separated string
var csv = names.Aggregate((a, b) => $"{a}, {b}");

It’s the general-purpose reduce - Sum, Count, Min, and Max are all specialized aggregates. I use the explicit Aggregate only when there’s no built-in for the fold I need, because a named operator reads better. For strings specifically I’d prefer string.Join over an Aggregate - clearer and faster.

Red flag answer: “I use Aggregate to sum numbers.” - That works, but Sum() is clearer; Aggregate earns its place for custom folds, not for things with a dedicated operator.

Q21. Any vs All, and Why Prefer Any() Over Count() > 0?

Mid

Any() returns true if at least one element matches (or any exists, with no predicate). All() returns true if every element matches - and importantly, All() on an empty sequence returns true (vacuous truth), which surprises people.

The performance point interviewers want: to check existence, use Any(), not Count() > 0.

if (orders.Any(o => o.IsOpen)) { ... } // stops at the first match
if (orders.Count(o => o.IsOpen) > 0) { ... } // counts every match first

Any() short-circuits on the first hit; Count() walks the entire sequence. In EF Core the gap is even bigger: Any() translates to a SQL EXISTS that can stop early, while Count() > 0 issues a full COUNT(*). Same answer, more work.

Red flag answer:Count() > 0 and Any() are equivalent.” - Same result, but Any() can stop at the first element and Count() can’t.



Modern .NET and Performance

These separate candidates who keep current from candidates stuck on .NET 6 patterns. Expect at least one performance scenario in a senior round.

Q22. What Does CountBy Add in .NET 9, and What Does It Replace?

Mid

CountBy (added in .NET 9) counts elements grouped by a key in one call, returning IEnumerable<KeyValuePair<TKey, int>>. It replaces the common GroupBy(...).Select(g => ... g.Count()) pattern, and does it without allocating an intermediate collection for each group:

// .NET 9
var perDept = employees.CountBy(e => e.Department);
foreach (var (dept, count) in perDept)
Console.WriteLine($"{dept}: {count}");
// What it replaces - GroupBy materializes each group just to count it
var perDept = employees
.GroupBy(e => e.Department)
.Select(g => new { Dept = g.Key, Count = g.Count() });

The win is both readability and allocation: GroupBy builds a group (a sub-collection) per key before you count it, while CountBy just keeps a running tally per key. On large in-memory sequences that’s a measurable difference.

Red flag answer: “Never heard of it, I’d always use GroupBy.” - GroupBy still works, but not knowing CountBy signals you stopped at .NET 8.

Q23. What Are AggregateBy and Index, Added in .NET 9?

Mid

Two more .NET 9 additions that clean up patterns people used to hand-roll:

AggregateBy - keyed aggregation in one pass, like CountBy but for any fold, not just counting. It avoids allocating a group per key:

// Total salary per department, no intermediate groups
var totals = employees.AggregateBy(
e => e.Department, // key
seed: 0m, // starting accumulator per key
(total, e) => total + e.Salary);

Index - pairs each element with its position, returning IEnumerable<(int Index, T Item)>. It replaces the clumsy Select((item, i) => ...) overload:

foreach (var (i, name) in names.Index())
Console.WriteLine($"{i}: {name}");

I bring these up to show I track the BCL. They’re small, but Index() in particular removes a footgun - the Select index overload is easy to misremember.

Red flag answer: “I’d track the index with a separate counter variable.” - That works, but Index() exists exactly so you don’t have to.

Q24. A Query Is Slow Because It Filters After Loading Everything. What’s the Bug?

Senior

The single most common LINQ performance bug: materializing too early, then filtering in memory.

// BUG: ToList() pulls the ENTIRE table into memory, THEN filters in C#
var open = db.Orders.ToList().Where(o => o.IsOpen).Take(20);
// FIX: filter and page in SQL, materialize only the 20 rows you need
var open = await db.Orders.Where(o => o.IsOpen).Take(20).ToListAsync();

The first version runs SELECT * FROM Orders, loads every row, and discards almost all of them. The fix keeps the query as IQueryable so Where and Take become a SQL WHERE and TOP/LIMIT, and only the final 20 rows cross the wire. The rule I state: materialize last, after every filter, projection, and page has been applied, never before.

Red flag answer: “Add .ToList() early so the rest is simpler.” - That’s the bug, not the fix; it forces a full table load.

Follow-up: “How would you confirm the filter actually ran in SQL?”

Q25. When Is LINQ the Wrong Tool?

Senior

LINQ is the right default for clarity, but it isn’t free. Each operator in a chain allocates an iterator, lambdas can allocate closures, and the per-element delegate calls aren’t free. On a genuinely hot path - a tight loop running millions of times, or a low-allocation routine - a plain for/foreach loop, or working over a Span<T> (which LINQ doesn’t support directly), can be meaningfully faster and allocate less.

My rule: write LINQ first because it’s readable, and only rewrite a specific hot path as a manual loop after a profiler shows the LINQ version is actually costing you. Premature de-LINQ-ing trades readability for a win you can’t measure.

Red flag answer: “LINQ is always slower, I avoid it.” - For 99% of code the readability wins and the cost is negligible; blanket-avoiding LINQ is a bigger mistake than the rare hot-path allocation.


EF Core and Real-World

LINQ over a database is where the language meets reality. These come up in any role that touches data.

Q26. How Do You Page Results With Skip and Take, and What’s the Catch at Scale?

Mid

Skip(n).Take(m) is the standard offset pagination - skip the rows before the page, take the page size:

var page = await db.Products
.OrderBy(p => p.Id) // ordering is required for stable paging
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();

Two things I always mention. First, you must order before paging - without OrderBy the database has no defined row order, so pages can overlap or skip rows. Second, offset pagination degrades on deep pages: Skip(100000) still makes the database count past 100,000 rows. For large datasets, keyset (cursor) pagination - Where(p => p.Id > lastSeenId).Take(pageSize) - stays fast because it seeks on an index instead of counting.

Red flag answer: “Just Skip and Take, it’s fine.” - Fine for page 2, slow for page 5,000, and broken without an OrderBy.

Read next

Pagination, Sorting, and Searching in ASP.NET Core Web API

Offset and keyset pagination, dynamic sorting, and building reusable IQueryable extensions - the full implementation behind this question.

Q27. Your Endpoint Returns 50 Rows but the Logs Show 51 Queries. What Is This?

Senior

That’s the N+1 query problem: one query loads the 50 parent rows, then EF Core fires one more query per row to load a related navigation property - 51 round trips for data one join could have fetched. It usually comes from lazy loading, or from iterating a navigation property you forgot to Include.

LINQ is where you both cause and fix it. The fix is to load related data in one query with Include, or better, project only the fields you need with Select into a DTO so EF emits a single joined query with no over-fetching. I catch it early by turning on EF Core query logging in development - a burst of identical parameterized queries is the tell.

Red flag answer: “I’d increase the connection pool size.” - That scales the symptom; the cause is the query shape.

Read next

30 EF Core Interview Questions That Actually Get Asked in 2026

The full data-access set - N+1 in depth, change tracking, migrations, ExecuteUpdate, concurrency, and query translation. The sibling page to this one.

Q28. When Do You Use AsNoTracking, and What’s the Catch?

Mid

AsNoTracking tells EF Core not to create change-tracking snapshots for the entities a query returns. For read-only queries - GET endpoints, reports, anything you won’t modify - that’s a real saving in memory and CPU, so I add it by default to read queries.

The catch interviewers probe: with no tracking, EF Core doesn’t do identity resolution by default, so if the same row appears twice in a result (through a join, say) you get two separate object instances instead of one shared reference. Use AsNoTrackingWithIdentityResolution if that matters. And you obviously can’t call SaveChanges to persist an entity you never tracked.

Red flag answer: “I put AsNoTracking on everything because it’s faster.” - Then how do your updates work? It only suits queries you won’t save.

Read next

Tracking vs No-Tracking Queries in EF Core

How change tracking works, when AsNoTracking helps, identity resolution, and the exact trade-offs behind this question.

Q29. How Do You Run a LINQ Query Asynchronously, and Why Can’t You Await a Plain Query?

Mid

You await the materializing operator, not the query itself. EF Core ships async versions of the execution operators - ToListAsync, FirstOrDefaultAsync, SingleOrDefaultAsync, CountAsync, AnyAsync - and those are what you await:

var open = await db.Orders.Where(o => o.IsOpen).ToListAsync();
var one = await db.Orders.FirstOrDefaultAsync(o => o.Id == id);

You can’t await db.Orders.Where(...) because Where is deferred and returns an IQueryable - there’s no operation in flight to await yet. The query only runs when an execution operator enumerates it, so the async version of that operator is what returns a Task. The standard System.Linq operators have no async variants; the async ones come from EF Core (Microsoft.EntityFrameworkCore).

Red flag answer: “I’d wrap the query in Task.Run to make it async.” - That just moves a blocking call to a thread-pool thread; the real async path is ToListAsync and friends.

Q30. How Do You Avoid Over-Fetching With a Projection?

Mid

Projecting with Select into a DTO tells EF Core to fetch only the columns you actually use, instead of loading whole entities:

// Pulls back 3 columns, no change tracking, one query
var dtos = await db.Orders
.Select(o => new OrderDto(o.Id, o.Total, o.Customer.Name))
.ToListAsync();

For a read-only GET this is my default over Include, which loads full related entities you don’t need. Projection also sidesteps tracking overhead and the cartesian-explosion problem you hit when you Include several collections at once (for that case, EF Core’s AsSplitQuery() issues one query per collection instead of one giant join). The principle: ask the database for the shape you’re returning, not the shape of your tables.

Red flag answer: “I load the entity and map it in C#.” - That over-fetches every column and tracks entities you’ll throw away; the projection does the trimming in SQL.


5 LINQ Interview Mistakes That Get Developers Rejected

After interviewing plenty of .NET developers, these are the patterns that sink an otherwise solid candidate:

  1. Not knowing when the query runs. If you can’t explain deferred execution, every answer about performance and multiple enumeration falls apart.
  2. Treating IEnumerable and IQueryable as the same thing. This is the difference between a SQL WHERE and a full table load in memory. Interviewers test it on purpose.
  3. Reaching for AsEnumerable to silence a translation error. It “fixes” the exception by loading everything into memory - the opposite of a fix.
  4. Filtering after materializing. ToList().Where(...) pulls the whole table back, then throws most of it away. The most common LINQ perf bug there is.
  5. Being stuck two versions back. Not knowing CountBy/Index (.NET 9) or the native LeftJoin/RightJoin (.NET 10) signals you stopped learning. Know what changed.

Key Takeaways

  • Deferred execution is the core concept. Queries describe work; they run when you enumerate. This explains multiple enumeration, the loop-variable trap, and why async uses ToListAsync.
  • IQueryable becomes SQL, IEnumerable runs in memory. Keep queries as IQueryable until the last moment so filters and paging push down to the database.
  • Materialize last. Apply every filter, projection, and page before you call ToList - never before.
  • Know the modern operators. CountBy, AggregateBy, Index (.NET 9) and native LeftJoin/RightJoin (.NET 10) are current-platform signals.
  • Every strong answer names a trade-off. State what LINQ does, where it runs, and what it costs.
Free resource Companion download

.NET Interview Questions

300+ real .NET interview questions with answers, red flags, and follow-ups - C#, EF Core, ASP.NET Core, system design

What is deferred execution in LINQ?

Deferred execution means a LINQ query is not run when you define it with operators like Where, Select, or OrderBy. Instead the query is a description of work that runs only when you enumerate it, by calling ToList, ToArray, Count, First, or using foreach. For LINQ to Objects this is implemented with iterator methods using yield return, and for IQueryable the operators build an expression tree that is sent to the database when enumerated. Deferred execution is why enumerating the same query twice runs it twice and why async LINQ awaits ToListAsync rather than the query itself.

What is the difference between IEnumerable and IQueryable in LINQ?

IEnumerable represents an in-memory sequence whose LINQ operators take Func delegates and run as compiled C# inside your process. IQueryable represents a query against an external source whose operators take Expression of Func and build an expression tree that a provider such as EF Core translates into SQL and runs in the database. The practical effect is where filtering happens: with IQueryable a Where clause runs as SQL and returns only matching rows, while casting to IEnumerable pulls all rows into memory first and filters in C#. Keep a query as IQueryable until the last moment so filters and paging push down to the database.

What is the difference between First and FirstOrDefault in LINQ?

First returns the first element that matches and throws an InvalidOperationException if the sequence is empty. FirstOrDefault returns the first matching element or the type default, which is null for reference types, when there is no match and does not throw. Use First when an empty result is genuinely an error you want to surface, and FirstOrDefault when no match is a valid case you will handle by checking for null. Single and SingleOrDefault are stricter still, throwing when more than one element matches.

What is the difference between Select and SelectMany?

Select projects each element into a single result, a one-to-one transformation that preserves the shape of the sequence. SelectMany projects each element into a sequence and then flattens all those sequences into one, a one-to-many transformation. If each customer has a list of orders, Select returns an IEnumerable of lists of orders while SelectMany returns a single flat IEnumerable of all orders. In query syntax a second from clause compiles to SelectMany.

Why should you use Any instead of Count greater than zero in LINQ?

Any returns true as soon as it finds the first matching element and then stops, while Count walks the entire sequence to produce a total before you compare it to zero. For an existence check Any is more efficient because it short-circuits. In EF Core the difference is larger because Any translates to a SQL EXISTS query that can stop at the first row, while Count greater than zero issues a full COUNT over the table. Both return the same answer, but Any does less work.

What new LINQ methods were added in .NET 9 and .NET 10?

NET 9 added CountBy, AggregateBy, and Index to System.Linq. CountBy counts elements grouped by a key without allocating a collection per group, AggregateBy performs a keyed aggregation in one pass, and Index pairs each element with its position as a tuple. NET 10 added native LeftJoin and RightJoin operators to both Enumerable and Queryable, which replace the older GroupJoin plus SelectMany plus DefaultIfEmpty pattern for outer joins. EF Core 10 translates LeftJoin and RightJoin to SQL LEFT JOIN and RIGHT JOIN.

What is the multiple enumeration problem in LINQ?

Multiple enumeration happens when you hold a deferred IEnumerable query and enumerate it more than once, for example calling Any, then Count, then a foreach on the same variable. Because the query is a deferred description rather than a stored result, each enumeration re-runs the whole query, which for a database source means hitting the database every time. The fix is to materialize the query once with ToList or ToArray and work with the resulting collection. Analyzers flag this as a possible multiple enumeration of IEnumerable because it is a common and real performance bug.

Why does EF Core throw could not be translated for a LINQ query?

EF Core throws could not be translated when a LINQ query contains an expression it cannot turn into SQL, typically a custom C# method or a .NET API with no SQL equivalent inside a Where or Select. Since EF Core 3.0 the framework refuses to silently evaluate that part on the client because doing so used to cause invisible full table scans. The fixes are to rewrite the predicate to compare raw columns EF can translate, move only the untranslatable part after an explicit AsEnumerable once the result set is already small, or push the logic into a computed column or raw SQL.


If a question here exposed a gap, the FREE .NET Web API Zero to Hero course covers LINQ and EF Core in order - querying, projection, tracking, and performance - with runnable code and no paywall. It’s the fastest way to turn a shaky LINQ answer into a confident one.

This is one spoke of my broader interview prep series. Start at the .NET interview questions hub for the cross-topic greatest hits, read the EF Core interview questions for the data-access layer, and the .NET Web API interview questions for the API on top.

If this helped, bookmark it for your next interview, or share it with someone prepping for theirs.

Happy Coding :)

View all articles

What's your take?

Push back, share a war story, or ask the obvious question someone else is wondering. I read every comment.

View on GitHub

Weekly .NET tips · free

Newsletter

stay ahead in .NET

One email every Tuesday at 7 PM IST. One topic, deep. The week's articles. No filler.

Tutorials Architecture DevOps AI
Join 9,735 developers · Delivered every Tuesday
Privacy notice 30s read

Cookies, but only the useful ones.

I use cookies to understand which articles get read and which CTAs actually work. No third-party advertising trackers, ever. Read the privacy policy →