Ryz Labs/Interview questions/C# / .NET developer
Interview questions

C# and .NET interview questions for senior developers (2026)

Scenario-based C# questions for .NET 9 and .NET 10: EF Core traps, DI lifetimes, async deadlocks, thread pool starvation, Native AOT and real migration plans.

These 27 C# and .NET interview questions are built for hiring engineers who will own ASP.NET Core services in production, not just write controllers. They cover the language through C# 14, EF Core, dependency injection, async behavior, memory and the runtime, and the migration and resilience work that fills a senior .NET engineer's week. When Ryz vets C# developers, recruiters first check what candidates built and ran, then candidates complete structured NTRVSTA AI interviews on topics like these, and recruiters review them before and after. The AI scores are advisory. People decide who moves forward.

How to use these questions

Many .NET candidates come from long careers on .NET Framework, and some have never shipped on modern .NET. Use the fundamentals to confirm they write current C#, then spend senior interviews on the async and runtime section, where production incidents actually come from.

Fundamentals

An EF Core query loads two million rows into memory before filtering. What went wrong?

The query was materialized or cast to IEnumerable<T> before the Where, so the filter ran in memory as LINQ to Objects instead of being translated to SQL. Common culprits are a ToList() too early, a repository method returning IEnumerable, or a filter calling a C# method EF cannot translate. Keep the query as IQueryable<T> until the final projection.

What a strong answer shows: They know the difference between expression trees and delegates, and check the generated SQL.

Why might a LINQ query hit the database twice?

LINQ is lazily evaluated. Each enumeration, such as calling Count() and then iterating with foreach, runs the query again. Materialize once with ToListAsync() when you need the results more than once.

What a strong answer shows: A clear model of deferred execution and its cost against a real database.

When would you choose a struct over a class?

Use a struct for small, immutable values with no identity, such as a coordinate or a money amount, where avoiding heap allocation matters. Large structs are copied on every assignment, and structs get boxed when cast to an interface or object, which can erase the benefit. readonly struct and record struct make intent explicit.

What a strong answer shows: Awareness of copying and boxing costs rather than "structs are faster".

What do nullable reference types guarantee, and what don't they?

They are compile-time analysis only. With <Nullable>enable</Nullable>, the compiler warns when you dereference something that might be null, but nothing changes at runtime. Deserialized JSON, reflection, the ! operator and unannotated libraries can all put nulls where the types say none exist, so validate at the boundaries.

What a strong answer shows: They treat warnings as errors in new code and still validate external input.

What do records give you, and what is the catch with with expressions?

Records provide value-based equality, a readable ToString, deconstruction and non-destructive mutation through with. The catch is that with makes a shallow copy: a record containing a List<T> shares that list with its copy.

public record Order(Guid Id, IReadOnlyList<Line> Lines, OrderStatus Status);

var shipped = order with { Status = OrderStatus.Shipped };
// shipped.Lines and order.Lines are the same list instance

What a strong answer shows: They know value equality compares references for collection members, and use immutable collections where it matters.

A singleton service holds a DbContext and starts throwing threading errors under load. Why?

This is a captive dependency. DbContext is registered as scoped and is not thread-safe, but a singleton that receives it keeps one instance for the life of the app and shares it across concurrent requests. Fix it by making the consumer scoped, or by injecting IDbContextFactory<T> or IServiceScopeFactory and creating a short-lived context per operation. Scope validation in Development catches this at startup.

What a strong answer shows: Fluency with DI lifetimes and the real failure mode of mixing them.

What is the difference between IDisposable, IAsyncDisposable and a finalizer?

Dispose releases resources deterministically, usually through a using declaration. DisposeAsync does the same for resources that need async cleanup, such as flushing a stream, with await using. A finalizer is a last-chance cleanup the GC runs at an unknown time; it delays collection and is rarely needed now that SafeHandle wraps native handles.

What a strong answer shows: They know finalizers are a cost, not a pattern to copy.

Intermediate

An order page loads slowly and EF Core logs show one query per order line. How do you fix it?

Lazy loading or a loop over navigation properties causes N+1 queries. Use Include and ThenInclude, or better, project straight to a DTO with Select so EF fetches only the needed columns. For read-only paths add AsNoTracking(). If several collection includes create a large cartesian product, AsSplitQuery() trades one big join for a few smaller queries.

What a strong answer shows: Projection as the default for reads, and the cartesian explosion trade-off.

A service creating new HttpClient() per request runs out of sockets. What is the right pattern?

Disposing HttpClient leaves sockets in TIME_WAIT, so a client per request exhausts ports. A single static client fixes that but ignores DNS changes. Use IHttpClientFactory with typed clients, or a long-lived client whose SocketsHttpHandler sets PooledConnectionLifetime.

What a strong answer shows: They know both failure modes, sockets and stale DNS.

Requests to an [Authorize] endpoint succeed without a token. What do you check in the middleware pipeline?

Middleware runs in registration order. UseAuthentication must come before UseAuthorization, and both after UseRouting when routing is explicit. Also check for a fallback policy, an [AllowAnonymous] on the controller, and whether the endpoint is a minimal API that needs RequireAuthorization().

What a strong answer shows: They understand the pipeline as ordered code, not configuration.

When do you use IOptions, IOptionsSnapshot and IOptionsMonitor?

IOptions<T> is a singleton read once. IOptionsSnapshot<T> is scoped and re-reads config per request, so it cannot be injected into singletons. IOptionsMonitor<T> is a singleton that sees changes and raises OnChange. Add ValidateDataAnnotations() and ValidateOnStart() so bad config fails the deploy, not the first request.

What a strong answer shows: Lifetimes again, and a habit of failing fast on configuration.

How do modern pattern matching features make business rules easier to read?

Switch expressions with property, relational and list patterns replace nested if/else chains, and the compiler warns when a case is missing.

decimal Shipping(Order o) => o switch
{
{ Total: >= 100m } => 0m,
{ Address.Country: "US", Lines.Count: <= 2 } => 5m,
{ Address.Country: "US" } => 8m,
_ => 25m
};

What a strong answer shows: They use patterns for clarity, order cases from most to least specific and know arms are evaluated top to bottom.

Minimal APIs or controllers for a new service?

Minimal APIs have less ceremony, start faster, work well with Native AOT and suit small, focused services. Route groups and endpoint filters cover most cross-cutting needs. Controllers still make sense for large APIs that rely on filters, conventions and model binding features the team already knows. Consistency within a codebase matters more than the choice.

What a strong answer shows: A pragmatic call grounded in team and deployment needs.

Async, memory and the runtime

Why is async void dangerous?

The caller cannot await it, so it cannot know when the work finishes, and an exception thrown inside it is raised on the synchronization context or thread pool, which can crash the process. It exists for event handlers. Everywhere else return Task.

What a strong answer shows: They know the crash behavior, not just the style rule.

A legacy app hangs when it calls GetDataAsync().Result. Why, and does the same happen in ASP.NET Core?

In classic ASP.NET or a UI app, the awaited continuation tries to resume on the captured synchronization context, which is blocked by .Result, so both wait forever. ConfigureAwait(false) in library code avoids capturing the context. ASP.NET Core has no synchronization context, so it does not deadlock this way, but blocking on async still wastes thread pool threads and leads to starvation under load.

What a strong answer shows: They know why the deadlock happens and why sync-over-async is still wrong on modern .NET.

Latency climbs from 50 ms to several seconds during traffic bursts, but CPU stays low. What do you suspect?

Thread pool starvation. Blocking calls such as .Result, Thread.Sleep or synchronous I/O hold threads, and the pool only adds threads gradually, so queued work waits. Confirm with dotnet-counters, watching thread pool queue length and thread count, and use dotnet-stack or a dump to see where threads are blocked. Fix the blocking calls rather than raising the minimum thread count.

What a strong answer shows: The low-CPU, high-latency signature and the right diagnostic tools.

How should cancellation flow through an ASP.NET Core request?

Accept a CancellationToken in the endpoint, which ASP.NET Core binds to HttpContext.RequestAborted, and pass it to every async call. Link it with a timeout when calling slow dependencies.

app.MapGet("/report/{id}", async (int id, ReportService svc, CancellationToken ct) =>
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(10));
return await svc.BuildAsync(id, cts.Token);
});

What a strong answer shows: They thread tokens all the way down and avoid wasting work for clients that already left.

When does ValueTask beat Task?

When a method usually completes synchronously, such as a cache hit, ValueTask avoids allocating a Task. It has rules: await it once, do not await it concurrently, and do not call .Result before it completes. For most application code Task is the right default.

What a strong answer shows: They know the constraints and do not apply it everywhere.

How would you cut allocations in a hot parsing path?

Profile first with dotnet-counters and a memory profiler. Then avoid Substring and Split by slicing ReadOnlySpan<char>, rent buffers from ArrayPool<T>, and use stackalloc for small fixed buffers.

static int SumCsv(ReadOnlySpan<char> line)
{
int total = 0;
foreach (Range r in line.Split(','))
total += int.Parse(line[r]);
return total;
}

Spans cannot live on the heap or cross an await; use Memory<T> there.

What a strong answer shows: Measured optimization and the span restrictions that come with it.

How does the .NET GC affect a service, and which settings matter?

The GC is generational, and objects of 85,000 bytes or more go to the large object heap, which is collected with gen 2. Server GC uses a heap per core for throughput; Workstation GC uses less memory. In containers, memory limits shape heap sizing, and since .NET 9 Server GC adapts its heap count to load by default (DATAS), which lowers memory use. Watch gen 2 collection rate, pause time and LOH allocations.

What a strong answer shows: They tie GC modes to deployment shape and know which metrics to watch.

You need to limit a background job to five concurrent API calls. What do you use?

lock cannot contain an await, so use SemaphoreSlim(5) with WaitAsync and release in a finally, or Parallel.ForEachAsync with MaxDegreeOfParallelism = 5. For producer and consumer pipelines, a bounded Channel<T> gives backpressure. For plain synchronous locking, .NET 9 added System.Threading.Lock.

What a strong answer shows: The right primitive for async code and a reason for each choice.

Senior and architecture

How would you move a large .NET Framework 4.8 web app to .NET 10?

Inventory blockers first: System.Web, WCF services, Web Forms, AppDomains and Windows-only libraries. Move class libraries to .NET Standard 2.0 or multi-target them, then migrate incrementally using a strangler approach with YARP in front, routing one area at a time to a new ASP.NET Core app. WCF servers can move to CoreWCF or gRPC. The .NET Upgrade Assistant helps with project files, but the hard parts are behavioral.

What a strong answer shows: An incremental plan that keeps shipping, not a big-bang rewrite.

When would you publish a service with Native AOT?

Native AOT gives fast startup and small memory use, which suits serverless functions, CLIs and dense container deployments. The cost is no runtime code generation: reflection-heavy libraries, dynamic proxies and some serializers break or need source generators, such as System.Text.Json's JsonSerializerContext. Treat trimming warnings as errors and test the published binary, not just the debug build.

What a strong answer shows: They know what breaks and how to verify compatibility.

How do you make outbound calls resilient without stacking retries on retries?

Use Microsoft.Extensions.Http.Resilience, built on Polly v8, and add AddStandardResilienceHandler() to the typed client for timeouts, retries with jitter and a circuit breaker. Retry only idempotent operations, set a total timeout, and make sure upstream callers do not also retry, or one slow dependency turns into a retry storm.

What a strong answer shows: System-level thinking about retries, not just adding a library.

Design reliable background processing for an ASP.NET Core app that sends invoices.

Write the invoice and an outbox row in one transaction, then have a BackgroundService or a dedicated worker poll the outbox and publish to a queue such as Azure Service Bus or SQS. Make handlers idempotent and honor the stopping token for graceful shutdown. Hangfire or Quartz.NET fit when you need scheduling and a dashboard.

What a strong answer shows: Durability across crashes and deploys, plus idempotency.

How would you isolate tenant data in a multi-tenant EF Core application?

Options range from a shared schema with a TenantId column, to a schema per tenant, to a database per tenant. With a shared schema, global query filters (HasQueryFilter) apply the tenant automatically, but code using IgnoreQueryFilters or raw SQL bypasses them, so add tests and consider database row-level security as a backstop.

What a strong answer shows: Trade-offs between cost and isolation, and defense in depth.

What does good observability look like for a .NET service?

Structured logging through ILogger with message templates, plus the [LoggerMessage] source generator on hot paths. OpenTelemetry for traces and metrics, with ASP.NET Core, HttpClient and EF Core instrumentation and exporters to the team's backend. Health checks for readiness and liveness. .NET Aspire helps wire this up locally with a dashboard.

What a strong answer shows: Correlated logs, traces and metrics, and attention to logging cost.

Red flags to watch for

A practical exercise

Share an ASP.NET Core service on .NET 10 with EF Core and SQL Server or Postgres in a container. It has an endpoint that returns customer statements and calls a flaky mock pricing API. Plant problems: an N+1 query, a scoped service captured in a singleton, a .Result call, and no cancellation or timeout on the outbound call. In a 90-minute pairing session, ask the candidate to make the endpoint fast and stable under a short load test with a tool such as k6 or bombardier.

Hire senior C# and .NET developers vetted with these questions

Ryz can introduce senior C# and .NET developers who have already been assessed on this material. They are the top 1% of the candidates we interview, they work on your team, in your repos and standups, and they keep hours within ±1h of US time zones. See the steps of our vetting process, or use our C# / .NET developer job description as a starting point.

FAQ

Should I test .NET Framework knowledge in 2026?

Only if you maintain Framework apps. In that case, ask about migration rather than Web Forms trivia: what blocks a move to modern .NET and how they would ship incrementally. For new services, test modern .NET and ASP.NET Core.

How do I test async skills in a remote interview?

Share a short snippet with a hidden deadlock, a missing await or an async void and ask the candidate to review it aloud. Code review shows more about async understanding than asking for definitions.

Is a take-home or live coding better for .NET roles?

Live pairing on an existing codebase is usually better for senior roles because most of the job is changing code someone else wrote. A short take-home works for candidates who prefer it, as long as you review it with them afterward.

Questions we didn't answer? Email info@ryzlabs.com.

Explore Ryz Labs

Staff augmentationDedicated development teamsAI pod teamsForward deployed engineersNearshore software developmentAI engineering teamsHire engineers by roleRyz Labs vs competitorsAlternatives guidesBuyer guidesCase studiesHow we vet engineers
Ryz Labs

Senior engineers in your time zone. AI pod teams that ship.

Tell us who you need. You'll get a scoped plan, a price and the names of the people who would do the work.

Start a conversation →