ASP.NET Core
What scoped, singleton, and transient mean
Every service registration picks one of three lifetimes.
AddSingleton: one instance for the whole application, created on first use and disposed at shutdown. Because every request shares it, it has to be thread-safe.AddScoped: one instance per client request in a web app. Everything resolved during that request shares it, and it is disposed when the request ends.AddTransient: a new instance every time it is requested.
Transient is not the cheapest option. A new object per resolution means per-request allocations, so a transient pulled ten times in one request builds ten instances. Pick the lifetime by how state needs to be shared, not by a hunch about performance.
The captive dependency trap
A longer-lived service that holds a shorter-lived one freezes the short one to its own lifetime. The headline case is a singleton that takes a scoped AppDbContext in its constructor. AddDbContext registers the context as scoped, so now one context is shared by every request. DbContext is not thread-safe, and concurrent requests throw A second operation was started on this context instance, with a change tracker that mixes entities across requests.
Wrong "I made the DbContext a singleton so it matches the singleton that uses it." That makes the shared, non-thread-safe context permanent, which is the bug itself.
Right inject IServiceScopeFactory into the singleton, create a scope per unit of work, resolve the context inside it, and dispose the scope. In the Development environment the container validates scopes when it is built and throws on this mistake; in Production that check is off by default, so an untested path can ship the bug.
Middleware order and short-circuiting
The pipeline runs middleware in the order you add it on the way in and in reverse on the way out. Use receives next and can run code before and after calling it, or short-circuit by not calling it. Run is terminal and has no next, so anything after the first Run never executes.
Order is load-bearing. UseAuthentication comes before UseAuthorization, UseCors before UseResponseCaching, and exception handling sits near the top so it catches everything downstream. UseStaticFiles runs early on purpose: files under wwwroot are public, and serving them before auth short-circuits cheaply.
EF Core change tracking and the N+1 query
A DbContext tracks the entities it loads, snapshots their original values, and on SaveChanges writes only the columns that changed. That per-instance state is why it is a short-lived, per-request unit of work.
For read-only queries call AsNoTracking to skip the snapshot and the tracking dictionary, which is faster and lighter. The classic performance bug is N+1: loop over parents and touch a navigation per item (or let lazy loading do it) and you fire one query for the parents plus one per parent. Load the related data in a single query with Include, or project the shape you need with Select.
Async all the way, and why blocking starves the pool
ASP.NET Core has no SynchronizationContext; SynchronizationContext.Current is null inside a request. The classic sync-over-async deadlock from WPF and legacy ASP.NET does not happen here, because a continuation resumes on whatever thread pool thread is free.
That does not make blocking safe. .Result or .Wait() holds a pool thread doing nothing while the async work still needs another pool thread to resume on, so one logical operation ties up two threads. Under load the pool drains faster than it grows, requests queue behind unavailable threads, and the app stops responding. No deadlock, but an outage.
Wrong "There is no deadlock in ASP.NET Core, so .Result is fine here." It trades a fast, obvious hang for a thread pool starvation that only surfaces under traffic. Stay async the whole call stack. async void is always wrong in a request: the handler returns at the first await, and a later write to the response can crash the process. Task.Run wrapped around synchronous work adds scheduling on the same pool that serves requests, without buying scalability.
IOptions, IOptionsSnapshot, and IOptionsMonitor
Bind a config section with Configure<T>(config.GetSection("...")), then choose the accessor by how fresh the value must be.
IOptions<T>is a singleton computed once. It never reflects config changes made after startup, and it injects anywhere.IOptionsSnapshot<T>is scoped and recomputed once per request, and it supports named options. Because it is scoped, injecting it into a singleton is the same captive-dependency violation as a scoped DbContext.IOptionsMonitor<T>is a singleton exposingCurrentValueplusOnChangecallbacks, so a singleton or background service uses it for config that must stay live.
The trap interviewers reach for: you need reloadable config inside a singleton, you inject IOptionsSnapshot<T>, and it throws at resolution. IOptionsMonitor<T> is the one that belongs there.
Eager loading, cartesian explosion, and migrations
Include fixes N+1 by fetching a parent and its children in one query. Push it too far and the opposite failure appears: joining several collection navigations duplicates the parent's columns across every combination of child rows, a cartesian explosion that bloats the result set. AsSplitQuery runs one query per collection instead, trading extra roundtrips (and the chance of data shifting between them) for no duplication.
Migrations diff the model against a checked-in snapshot. dotnet ef migrations add <Name> generates Up/Down, dotnet ef database update applies them, and applied migrations are recorded in __EFMigrationsHistory. Calling context.Database.Migrate() on startup suits local development; across several app instances they race to alter the schema, so production wants a scripted, controlled step.
Minimal APIs versus MVC controllers
Minimal APIs register handlers directly with app.MapGet and MapPost, bind parameters from route, query, body, and services, and group with MapGroup and endpoint filters. Microsoft recommends them for new projects for the lower overhead and simpler code.
[ApiController] on a controller adds behavior minimal APIs do not get for free: automatic 400 responses with a ValidationProblemDetails body when ModelState is invalid, binding-source inference, and Problem Details for error status codes. Reach for controllers when you need model-binding or validation extensibility, application parts, or OData, which they provide out of the box.
beta
The interviewer part is in the works.
The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.