ASP.NET Core Interviews: Middleware Order and DI Service Lifetimes
Two ASP.NET Core questions come up constantly, and both have a “knows the words” answer and a “has been burned” answer. Interviewers are listening for the second one.
Question 1: “Explain the middleware pipeline.”
Student answer: “Middleware components are registered in Program.cs and run in order. Each one can do work before and after calling next(), forming a request pipeline.”
Correct, and it tells the interviewer nothing. The pipeline being ordered is the whole point of the question — so lead with the consequence of ordering it wrong.
Engineer answer: “The pipeline is a nested chain — each middleware wraps everything after it, on the way in and the way back out. What matters in practice is that the order is a contract, not a preference:
- Exception-handling / problem-details middleware has to be near the top, or it can’t catch what happens below it.
UseRoutingmust come beforeUseAuthorization, because authorization needs to know which endpoint was matched to read its[Authorize]metadata.UseAuthenticationbeforeUseAuthorization— you can’t authorize a principal you haven’t established.UseCorshas to sit between routing and authorization, and a misplacedUseCorsis a classic bug: put it after the middleware that short-circuits, and preflightOPTIONSrequests get rejected before CORS headers are ever attached.- Static files early, so you don’t run auth on every image.
I’ve debugged a CORS issue that turned out to be exactly that ordering mistake — the browser reported a CORS failure, but the real cause was UseCors registered in the wrong place. The lesson I took: when something in the pipeline misbehaves, check the order before you touch the configuration.”
Follow-ups: “What’s the difference between Use, Run, and Map?” — Use can call the next middleware, Run is terminal, Map branches the pipeline on a path. “Middleware vs filters?” — middleware is framework-wide and runs before MVC even picks an action; filters are MVC-aware and can see the action, model state, and result.
Question 2: “Scoped, Transient, Singleton — when do you use each?”
Student answer: “Transient is a new instance every time it’s requested. Scoped is one instance per request. Singleton is one instance for the whole application.”
Again, true, and it’s the definition the interviewer already knows. The real question underneath is: “have you caused a bug by picking the wrong one?”
Engineer answer: “The lifetimes themselves are simple — the trap is mixing them. The captive-dependency problem is where a longer-lived service holds a reference to a shorter-lived one: inject a scoped service into a singleton and that scoped instance is now effectively a singleton, alive for the whole app, long after its request ended. The classic anti-pattern is injecting DbContext — which is scoped — into a singleton. You get a context that’s shared across requests and across threads, and DbContext isn’t thread-safe, so you start seeing random InvalidOperationExceptions about concurrent use, or data from one request leaking into another. In development the container’s scope validation usually catches the direct case at startup; the ones that slip through are indirect, or a manually constructed singleton.”
My defaults: stateless services that are cheap to construct — scoped, so they align with the request and can depend on other scoped things safely. Anything holding a DbContext or per-request state — scoped. Genuinely shared, thread-safe, expensive-to-build things like a configured HttpClient factory, a cache, or a background queue — singleton. Transient I use sparingly, mostly for lightweight things where I explicitly don’t want shared state. And when a singleton genuinely needs something scoped, it injects IServiceScopeFactory and creates a scope per unit of work instead of capturing one.”
Follow-ups: “Why is IHttpClientFactory the recommended pattern?” — a raw new HttpClient() per call exhausts sockets, a static one never picks up DNS changes; the factory pools handlers and rotates them. “How does a singleton use a scoped service in a hosted background service?” — IServiceScopeFactory.CreateScope() inside the work loop.
The pattern
Both answers are the same move: skip the definition the interviewer already has, name the specific failure the design guards against, attach one real diagnosis, and end with the rule you now apply. That structure — definition, cost, scenario, decision — is what makes a mid-level answer sound senior without adding any new facts.
A pipeline order I can reason through
I find it easier to remember middleware as dependencies rather than as a magic list. Exception handling needs to wrap the work it is supposed to catch. Routing has to identify an endpoint before authorization can inspect endpoint metadata. Authentication has to establish an identity before authorization can decide what that identity may do.
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();The exact pipeline changes with the application, but the reasoning does not. For every component, ask two questions: what information does this middleware need? and what must it wrap? That is much more reliable than memorizing a screenshot from documentation.
A DI lifetime test I use mentally
Before choosing a lifetime, I ask who owns the state and how long that state is valid. Request-specific state naturally belongs to a scope. Shared application state needs thread-safety. A disposable dependency should not accidentally live longer because a singleton captured it.
This also makes code review easier. If I see a singleton constructor accepting a repository backed by DbContext, I do not need to wait for a production exception. The lifetime graph already tells me something is wrong.
What I would check during a real incident
- Print or inspect the effective middleware order before changing CORS or authorization settings.
- Check whether a middleware short-circuits and prevents later components from running.
- Turn on scope validation in development.
- Trace singleton dependencies transitively, not only direct constructor parameters.
- For background work, create an explicit scope for each unit of work instead of holding request-scoped services.
I use this format for every .NET interview topic. The full set — ASP.NET Core internals, DI, EF Core, async, CLR and GC, system design — is an A4-printable prep system called .NET Job Interview OS, with weak-versus-strong answers side by side for each question.
The takeaway
Middleware order and DI lifetimes look like trivia. They are actually the two places in an ASP.NET Core app where a small, invisible mistake produces a confusing production bug — a CORS failure that isn’t about CORS, a threading exception that isn’t about your code. Interviewers ask them because the strong answer proves you’ve met that bug and know where to look next time.