EF Core Interview Questions: Tracking, the N+1 Problem, and IQueryable Traps
EF Core questions are database questions in disguise. The interviewer wants to know whether you have watched an ORM turn one endpoint into three hundred queries — and fixed it.
Three questions come up in almost every .NET interview that touches data access:
- “What’s the difference between tracking and no-tracking queries?”
- “What is the N+1 problem and how do you avoid it?”
- “What’s the difference between
IQueryableandIEnumerable?”
All three are really asking: do you know what SQL your C# produces, and have you been on the hook when it produced the wrong SQL at scale?
1. Tracking vs no-tracking
Student answer: “Tracking queries let EF Core detect changes so SaveChanges() works. AsNoTracking() skips that and is read-only.”
Engineer answer: “Tracking means EF Core keeps a snapshot of every entity it returns and diffs it on SaveChanges. That’s exactly what you want when you load an entity, mutate it, and save it in the same context. But on a read-heavy path — an API endpoint returning a list, a report, a dashboard — you’re paying to snapshot hundreds of rows you will never write back. I default read endpoints to AsNoTracking(), or set QueryTrackingBehavior.NoTrackingWithIdentityResolution at the context level when the read graph has shared references. I’ve seen a listing endpoint that was tracking by default sit at the top of a slow-query dashboard purely from change-tracking overhead and the extra allocations under load; switching it to no-tracking took a meaningful slice off the p95.”
The follow-up: “Does no-tracking make writes faster?” No — it’s irrelevant to writes; you need tracking (or manual Update/Attach) to save. The distinction is a read-path optimisation.
2. The N+1 problem
Student answer: “N+1 is when you run one query for a list and then one query per item for a related property. You fix it with Include().”
Engineer answer: “N+1 usually isn’t written on purpose — it’s lazy loading, or a .Select that touches a navigation property inside a loop, or an AutoMapper projection that walks children. You load 200 orders, then something accesses order.Customer per row, and you’ve quietly issued 201 queries. The tell in production is a endpoint whose latency scales linearly with result count and whose database shows a flood of near-identical parameterised selects. Fixes, in order of preference: project to a DTO with a single Select so EF builds one join; use Include / ThenInclude when you genuinely need the entities; and if a multi-collection Include causes cartesian explosion, switch to AsSplitQuery(). I also keep lazy loading proxies off by default so this can’t happen silently.”
Naming AsSplitQuery() and the cartesian-explosion trade-off (multiple round trips vs one bloated result set) is usually what tips this answer from “knows the term” to “has fixed this”.
3. IQueryable vs IEnumerable
Student answer: “IQueryable builds an expression tree and runs on the database; IEnumerable runs in memory with delegates.”
Engineer answer: “The classic anti-pattern here is a method that returns IEnumerable<T> from a repository. The moment you return IEnumerable, or call .ToList() / .AsEnumerable() too early, every filter after that point runs in application memory — so repo.GetAll().Where(x => x.IsActive) pulls the entire table across the wire and filters it in C#. I’ve traced a memory spike to exactly that: a helper returned IEnumerable, a caller added a .Where, and a table that was fine at 10,000 rows became a problem at two million. Keep the query as IQueryable until the last responsible moment, then materialise once. And be aware some LINQ operators don’t translate — EF Core will either throw or, in older versions, silently switch to client evaluation.”
The pattern across all three
Each answer follows the same shape the strong candidates use for every topic: state the definition briefly, name the real cost, attach a production scenario, then give the rule you now follow. Notice what the engineer answers have that the student answers don’t — a symptom you could actually observe on a dashboard, and a decision.
Follow-ups worth rehearsing
- “How do you find N+1 in an existing app?” Log generated SQL in development, watch query counts per request, or use an interceptor / MiniProfiler. In production: correlate endpoint latency with row count.
- “When is
Includeworse than a projection?” When you only need three columns of the child butIncludehydrates the whole entity, and when multipleIncludes multiply rows together. - “Why not one giant DbContext for the whole request?” DbContext isn’t thread-safe, the change tracker grows without bound on long-lived contexts, and stale tracked entities cause confusing bugs. Scoped-per-request, short-lived.
- “Bulk update of 100,000 rows?” Not a
foreachwithSaveChanges.ExecuteUpdate/ExecuteDelete(EF Core 7+), or drop to raw SQL / a bulk library.
Start with the SQL, not the LINQ
When an EF Core endpoint gets slow, I want to know what SQL actually reached the database. LINQ is a convenient way to express a query; it is not evidence that the generated query is efficient. Logging generated SQL and checking query count often makes an N+1 problem obvious within minutes.
var orders = await db.Orders
.AsNoTracking()
.Where(o => o.Status == OrderStatus.Open)
.Select(o => new OrderRow(
o.Id,
o.Customer.Name,
o.Total))
.ToListAsync();For a read-only screen, projection often gives me exactly what the UI needs without materializing a large tracked entity graph. That reduces transferred columns, tracking overhead and accidental lazy-loading surprises.
IQueryable is a promise, not data
An IQueryable<T> is still a query description. Operations added before materialization can be translated into SQL. Once ToList() or another materializer runs, later filtering happens in memory. That boundary is important enough that I try to keep it visible in repository and service code.
Quick EF Core review checklist
- How many SQL statements does one request execute?
- Are read-only queries using tracking unnecessarily?
- Are we loading entire entities when a projection would do?
- Did materialization happen before filtering, sorting or pagination?
- Are related entities loaded deliberately rather than accidentally?
- Does the database have an index that matches the real filter and sort pattern?
This is the format I use for every .NET interview topic: definition → cost → real scenario → decision. The complete set — EF Core, async, ASP.NET Core internals, CLR and GC, system design — is an A4-printable prep system called .NET Job Interview OS, with weak-versus-strong answers laid out side by side.
The takeaway
EF Core interview questions are a proxy for one skill: knowing what SQL your LINQ becomes, and noticing when it becomes the wrong SQL. If your answers stay at the level of “Include fixes N+1” you sound like someone who read the docs. If they include a dashboard symptom and a rule, you sound like someone who has paid for the lesson.