.NET & C# Development · Lesson 144 of 229
High-Priority Senior Topics — Deep Dive
Senior .NET interviews (especially enterprise and legal-tech roles) rarely test trivia. They test whether you have shipped production systems and can explain tradeoffs under pressure.
This guide is one consolidated section: the topics that return most often, answered the way a senior engineer should answer — with context, not definitions.
How to use this: For each topic, read the senior answer aloud. If you have BuildTrace, healthcare, or multi-tenant SaaS experience, swap in your real examples. Real incidents beat memorized theory.
How Senior Answers Differ
| Weak answer | Senior answer | |---|---| | "AsNoTracking improves performance." | "I use AsNoTracking on read-heavy list endpoints because change tracking adds memory and CPU. If the same request later updates entities, I keep tracking or reload explicitly." | | "Singleton is one instance for the app." | "Singleton is one instance per root scope — usually the process. I avoid injecting scoped services into singletons because that creates a captive dependency and stale DbContext." |
The pattern: when, why, what breaks, what you'd do instead.
1. Middleware Pipeline
Why interviewers care: Every HTTP request passes through this stack. Misordering middleware is a production bug, not a style choice.
Request flow (mental model)
Client → Kestrel → Middleware chain → Endpoint → Middleware (response) → ClientMiddleware runs in registration order on the way in, and reverse order on the way out.
Correct order (typical API)
var app = builder.Build();
app.UseExceptionHandler(); // outermost — catch everything downstream
app.UseHttpsRedirection();
app.UseRouting(); // must be before auth if endpoints use routing
app.UseCors();
app.UseAuthentication(); // BEFORE authorization
app.UseAuthorization();
app.UseRateLimiter();
app.MapControllers();Senior point: UseAuthentication before UseAuthorization is non-negotiable. Authorization without authentication only works for anonymous policies.
Authentication vs authorization
| | Authentication | Authorization |
|---|---|---|
| Question | Who are you? | What may you do? |
| Mechanism | JWT bearer, cookies, API keys | Policies, roles, resource handlers |
| Middleware | UseAuthentication | UseAuthorization |
| Example | Validate signature, expiry, issuer | [Authorize(Policy = "CanEditCase")] |
Production example: In a multi-tenant SaaS API, authentication establishes identity (user + tenant claim). Authorization checks tenant isolation — a user authenticated for Tenant A must never read Tenant B's data even with a valid token.
Short-circuiting
Middleware can stop the pipeline without calling _next:
public async Task InvokeAsync(HttpContext context)
{
if (!context.Request.Headers.ContainsKey("X-Api-Key"))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Missing API key");
return; // short-circuit — nothing below runs
}
await _next(context);
}Use for: health checks that skip heavy middleware, API key gates, maintenance mode, geo-blocking.
.NET 8+: app.MapGet("/health").ShortCircuit() bypasses the rest of the pipeline for that route — useful for load balancer probes.
Exception handling middleware
Two layers matter:
- Global handler —
UseExceptionHandlerorIExceptionHandler(.NET 8+) — maps unhandled exceptions to ProblemDetails, logs correlation ID, never leaks stack traces in production. - Developer page —
UseDeveloperExceptionPage()— development only.
Senior answer: "In production I return RFC 7807 ProblemDetails with a stable type URI and correlation ID. I log the full exception server-side. In development I use the developer page for fast debugging."
Custom middleware — when and how
Use custom middleware for cross-cutting HTTP concerns: correlation IDs, request timing, tenant resolution from subdomain, security headers.
app.Use(async (context, next) =>
{
var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
?? Guid.NewGuid().ToString();
context.Items["CorrelationId"] = correlationId;
context.Response.Headers["X-Correlation-Id"] = correlationId;
await next(context);
});Prefer IEndpointFilter or MediatR pipeline behaviors when the concern is per-endpoint business logic, not HTTP transport.
Classic interview question
"What happens if you put UseAuthorization before UseAuthentication?"
Authorization middleware runs without a populated HttpContext.User. Authenticated policies fail or behave inconsistently. Always authenticate first.
2. Dependency Injection Lifetimes
Why interviewers care: Wrong lifetimes cause subtle bugs — stale data, memory leaks, thread-safety issues — that only appear under load.
The three lifetimes
| Lifetime | Created | Typical use |
|---|---|---|
| Transient | Every injection | Lightweight, stateless helpers (IEmailTemplateRenderer) |
| Scoped | Once per scope (HTTP request in web apps) | DbContext, unit of work, per-request state |
| Singleton | Once per application | Caches, config wrappers, HttpClient via IHttpClientFactory |
Why DbContext is scoped
DbContext is not thread-safe and tracks entity state for one unit of work. One instance per request ensures:
- Change tracker doesn't mix entities from two concurrent requests
- Connection is borrowed from the pool for the request duration and returned
SaveChangesboundaries match business transactions
Why singleton cannot depend on scoped (captive dependency)
// WRONG — container may allow this at startup but behavior is broken
builder.Services.AddSingleton<ReportCacheService>();
builder.Services.AddScoped<AppDbContext>();
public class ReportCacheService
{
public ReportCacheService(AppDbContext db) { ... } // DbContext lives as long as singleton!
}What goes wrong:
- Stale DbContext — same context serves thousands of requests; tracked entities accumulate → memory growth, wrong data.
- Thread safety — multiple requests hit one context → corruption, random exceptions.
- Disposed context — if something disposes the scoped instance, singleton holds a dead reference.
Fixes:
- Inject
IServiceScopeFactoryand create a scope per operation - Use
IDbContextFactory<TContext>for singleton-hosted background work - Register the dependent as transient/scoped and restructure
public class ReportCacheService
{
private readonly IServiceScopeFactory _scopeFactory;
public async Task RefreshAsync()
{
await using var scope = _scopeFactory.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// fresh context, correct lifetime
}
}The question they always ask
"What problems happen if a singleton uses a scoped service?"
Senior answer: "You get a captive dependency — the scoped service is promoted to singleton lifetime. For DbContext that means one context shared across all requests: memory leaks from the change tracker, thread-safety violations, and connections held too long. I've seen APIs slow down over hours until restart. The fix is IServiceScopeFactory or IDbContextFactory for background/singleton code."
3. EF Core Performance
Why interviewers care: ORM mistakes are the #1 cause of slow APIs in production .NET teams.
IQueryable vs IEnumerable
| | IQueryable | IEnumerable | |---|---|---| | Executes | At database (SQL) | In memory (.NET) | | When to use | Filtering, paging, projecting in SQL | Data already in memory | | Risk | Forgetting to materialize early | Accidentally loading entire tables |
// GOOD — filter in SQL
var active = _context.Orders
.Where(o => o.Status == "Active")
.Take(20);
// BAD — loads ALL orders, then filters in memory
var active = _context.Orders.AsEnumerable()
.Where(o => o.Status == "Active")
.Take(20);Senior tip: Call .ToQueryString() or enable SQL logging in staging to verify what EF generates.
AsNoTracking
Use for read-only queries (lists, reports, dashboards).
var cases = await _context.Cases
.AsNoTracking()
.Where(c => c.TenantId == tenantId)
.Select(c => new CaseListDto { Id = c.Id, Title = c.Title })
.ToListAsync(ct);Tradeoff: No change tracking → updates require Attach + EntityState.Modified, or a separate tracked query. Don't blanket AsNoTracking on write paths.
N+1 problem
Symptom: 1 query for parents + N queries for children.
// N+1 — one query per order for items
var orders = await _context.Orders.ToListAsync();
foreach (var o in orders)
Console.WriteLine(o.Items.Count); // lazy load per orderFixes:
Include/ThenInclude— JOIN (watch cartesian explosion on multiple collections)AsSplitQuery()— separate SQL per collection, avoids huge JOIN result sets- Projection —
Selectinto DTOs (best for APIs) - Explicit batch:
Where(i => orderIds.Contains(i.OrderId))
Include performance
- Single
Include— usually fine - Multiple collection includes — cartesian product → use
AsSplitQuery() - Over-fetching —
Includeentire graphs when API needs 3 fields → use projection instead
Projection (preferred for APIs)
return await _context.Orders
.Where(o => o.TenantId == tenantId)
.Select(o => new OrderSummaryDto
{
Id = o.Id,
Total = o.Lines.Sum(l => l.Price * l.Quantity),
ItemCount = o.Lines.Count
})
.ToListAsync(ct);Only columns needed hit the wire and memory.
Pagination
Offset (Skip/Take):
- Simple, works with arbitrary page numbers
- Slow on large offsets — DB still scans skipped rows
Cursor (keyset):
WHERE Id > @lastId ORDER BY Id LIMIT 20— stable performance at scale- No random access to page 500 without walking cursors
Senior answer: "For admin UIs with small datasets I use offset. For high-volume feeds (audit logs, notifications) I use cursor pagination on an indexed, monotonic key."
Split queries
var orders = await _context.Orders
.Include(o => o.Lines)
.Include(o => o.Payments)
.AsSplitQuery()
.ToListAsync();Generates multiple SQL statements instead of one giant JOIN — often faster and less memory.
Indexes
EF doesn't replace indexing strategy:
- Index foreign keys and
WHERE/ORDER BYcolumns - Composite indexes match query patterns:
(TenantId, CreatedAt DESC) - Use
EXPLAIN ANALYZEin PostgreSQL/SQL Server — interviewers love "I'd check the execution plan"
Interview question
"Why is this query slow?"
Walk through: N+1? Missing index? Loading full entities vs projection? Tracking overhead? Network latency? Show you'd measure with Application Insights/SQL profiling before guessing.
4. Concurrency & Transactions
Optimistic concurrency
Assume conflicts are rare; detect at save time.
public class Case
{
public int Id { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; } = null!;
}On concurrent update → DbUpdateConcurrencyException. Return 409 Conflict to client with refresh instructions.
Senior answer for "two users update same record":
"With optimistic concurrency, the second save fails because the row version changed. The API returns 409; the client refetches and merges or the user resolves manually. For high-contention fields (inventory, seat booking) I'd consider pessimistic locking or a dedicated concurrency strategy — but for most business entities optimistic is the right default."
Transactions
await using var tx = await _context.Database.BeginTransactionAsync(ct);
try
{
await _context.Orders.AddAsync(order, ct);
await _outbox.PublishAsync(new OrderCreated(order.Id), ct);
await _context.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
}
catch
{
await tx.RollbackAsync(ct);
throw;
}Scope: Keep transactions short — no HTTP calls inside. Long transactions hold locks → deadlocks.
Deadlocks
Two transactions lock rows in opposite order → SQL Server/PostgreSQL picks a victim and aborts one.
Mitigations:
- Consistent lock ordering (always update Parent then Child)
- Shorter transactions
- Retry on deadlock error code with exponential backoff + jitter
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var tx = await _context.Database.BeginTransactionAsync(ct);
// work
await tx.CommitAsync(ct);
});EF Core execution strategy wraps retries for transient failures including deadlocks.
5. Background Services
IHostedService vs BackgroundService
BackgroundService is an abstract base implementing IHostedService with a ExecuteAsync loop — the standard choice for long-running work.
public class OutboxProcessor : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await ProcessBatchAsync(stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}Queues vs in-process
| Approach | When |
|---|---|
| Channel<T> / in-memory queue | Single instance, fire-and-forget within process |
| Hangfire / Quartz | Scheduled jobs, retries, dashboard, persistent storage |
| Azure Service Bus / RabbitMQ | Multi-instance, at-least-once, cross-service |
BuildTrace-style example: "We used a background worker to process outbox events so HTTP requests only wrote to the DB — publishing happened asynchronously with retries."
Hangfire vs Quartz.NET
- Hangfire — great for ad-hoc background jobs, retries, UI dashboard, SQL storage
- Quartz — cron-style scheduling, clustering, complex calendars
Cancellation tokens & graceful shutdown
On shutdown, stoppingToken is cancelled. Cooperate:
await foreach (var item in channel.Reader.ReadAllAsync(stoppingToken))
{
await ProcessAsync(item, stoppingToken);
}Senior point: "I avoid Task.Run fire-and-forget in ASP.NET — hosted services with proper cancellation integrate with Kestrel shutdown and Kubernetes terminationGracePeriodSeconds."
6. Configuration + Cloud Deployment
Configuration precedence (later wins)
appsettings.jsonappsettings.{Environment}.json- Environment variables (double underscore for nesting:
ConnectionStrings__Default) - Azure Key Vault / user secrets (dev)
- Command-line args
Options pattern
builder.Services.Configure<JwtSettings>(
builder.Configuration.GetSection("Jwt"));
public class TokenService
{
private readonly JwtSettings _settings;
public TokenService(IOptions<JwtSettings> settings) // snapshot at startup
=> _settings = settings.Value;
}| Interface | Behavior |
|---|---|
| IOptions<T> | Singleton snapshot — config frozen at startup |
| IOptionsSnapshot<T> | Scoped — reloads per request |
| IOptionsMonitor<T> | Singleton + change notifications — use when config reloads at runtime |
Senior answer: "I use IOptionsMonitor for feature flags that change without redeploy. For secrets like JWT signing keys I prefer Key Vault references over baking secrets into appsettings."
Secrets
- Never commit secrets to git
- Dev: User Secrets
- Production: Azure Key Vault, AWS Secrets Manager, environment variables in CI/CD (injected, not logged)
Azure deployment (typical answers)
| Option | Tradeoff | |---|---| | App Service | Managed, easy deploy slots, less container control | | AKS / Container Apps | Full control, Kubernetes complexity | | Docker on App Service | Middle ground — container image, managed platform |
Zero-downtime: Deployment slots (blue/green), health checks before swap, backward-compatible DB migrations.
Scale APIs: Horizontal pod/instance scaling on CPU/RPS, Redis for shared state, read replicas for heavy reads.
7. API Design
REST principles (pragmatic)
- Resources as nouns:
/cases/{id}, not/getCase - Correct verbs: GET (safe), POST (create), PUT (replace), PATCH (partial), DELETE
- Status codes: 201 + Location, 204 No Content, 400 validation, 404 not found, 409 conflict, 429 rate limit
Versioning
- URL:
/api/v2/cases— explicit, easy to route - Header:
Api-Version: 2— cleaner URLs - Senior: "I version when breaking contract — removing fields, changing semantics. Additive changes don't need a new version."
Validation
FluentValidation or data annotations + automatic 400 ProblemDetails. Validate at the boundary; domain validates invariants.
Consistent error responses (RFC 7807)
{
"type": "https://api.example.com/errors/validation",
"title": "Validation failed",
"status": 400,
"traceId": "00-abc-123",
"errors": { "Email": ["Invalid format"] }
}JWT auth (flow summary)
- Client authenticates → receives access token (short) + refresh token (long, stored securely)
- API validates: signature, issuer, audience, expiry, optional tenant claim
- Refresh rotates refresh token to detect theft
Rate limiting
.NET 7+ built-in AddRateLimiter — fixed/sliding window, per-user partition. Return 429 + Retry-After.
Swagger/OpenAPI
Development and partner onboarding — disable or protect introspection in production if sensitive.
Real Senior Questions They May Ask
Architecture
"How would you design a scalable API platform?"
Stateless API behind load balancer → horizontal scale. Cache hot reads (Redis). Async for heavy work (queues). DB: indexes, read replicas, connection pooling. Observability: traces, metrics, structured logs. Version APIs; rate limit by tenant.
"When would you split into microservices?"
When teams, deployment cadence, or scaling needs diverge — not by default. Start modular monolith; extract when a bounded context has clear ownership and independent scaling. Accept operational cost (distributed tracing, deployments, contracts).
"Would you use Clean Architecture everywhere?"
No — CRUD internal tools don't need four layers. Use Clean/Vertical Slice when domain complexity, testability, and long-lived business rules justify the ceremony. Pragmatism over purity.
Performance
"What causes memory leaks in .NET?"
Event handlers not unsubscribed, static caches growing unbounded, IDisposable not disposed (IHttpClient before factory), timers, capturing large objects in singletons, EF tracking too many entities.
"When does async hurt performance?"
Tiny CPU-bound work with async overhead; unnecessary Task.Run; too many concurrent I/O operations exhausting connection pool; sync-over-async blocking thread pool. Measure with BenchmarkDotNet and APM.
"How do you detect N+1?"
SQL logging, MiniProfiler, Application Insights dependency count spike, integration tests asserting query count.
Cloud & DevOps
"What should CI/CD contain?"
Build, test (unit + integration), SAST, container scan, deploy to staging, smoke tests, deploy to prod with approval/slots, migration step, rollback plan.
"What logs and metrics matter?"
RED/USE: request rate, errors, duration (p50/p95/p99), dependency failures, queue depth, DB connection pool, business KPIs (orders/hour). Structured logs with correlation ID.
Database
"When raw SQL instead of EF?"
Complex reports, bulk updates, database-specific optimizations, migrations of legacy queries. Use Dapper or FromSqlRaw with parameters — never string concatenation.
"Offset vs cursor pagination?" — see §3 above.
Async / threading
Task.WhenAll — parallel independent I/O; watch DbContext thread-safety (one context per parallel operation or separate scopes).
Race conditions — use locks, Interlocked, channels, or database constraints — not "it'll probably be fine."
Security (Often Asked)
| Topic | Senior talking point | |---|---| | JWT | Short-lived access, refresh rotation, validate issuer/audience, store refresh in httpOnly cookie or secure storage | | OAuth | Authorization server issues tokens; your API is resource server | | RBAC | Role checks at API; combine with resource-based auth for ownership | | OWASP | Injection (parameterized queries), broken auth, SSRF on webhooks, excessive data exposure | | SQL injection | EF parameterizes; raw SQL must use parameters | | Secrets | Key Vault, never in repo, rotate keys |
Questions You Should Ask Them
These signal senior maturity:
- "How do teams handle architecture decisions — ADRs, guilds, or tech lead driven?"
- "How much ownership do developers have over deployments?"
- "How do you monitor and respond to production incidents?"
- "What are the biggest technical challenges right now?"
- "How are systems hosted today — Azure, on-prem, hybrid?"
- "How do teams balance technical debt vs delivery?"
Likely Interview Flow
- Introduction / personality fit
- Past projects (go deep on your systems)
- Deep technical (this guide)
- Architecture discussion
- Cloud / DevOps
- Problem-solving scenario
- Team / collaboration
Less LeetCode-heavy; more production judgment.
Final Preparation Priority
Focus highest ROI for senior enterprise .NET roles:
- ASP.NET Core pipeline & middleware order
- DI lifetimes & captive dependencies
- EF Core performance (N+1, tracking, projection, pagination)
- Async/concurrency & transactions
- Azure deployment, Docker, configuration
- Architecture tradeoffs (monolith vs services, Clean Architecture when)
- API security (JWT, RBAC)
- Background services & graceful shutdown
- Real production incidents from your experience
Related: Complete Scenarios & Deep Dives · Senior .NET Interview Q&A (Q81–Q250) · Complete Answer Guide · .NET Interview Bootcamp