Debugging • Security • EF Core • Reliability • Production Calls • 2026

Scenario-Based .NET Interview Questions

Scenario-based .NET interviews describe an ASP.NET Core situation instead of asking for a definition, such as a body that binds as null, an endpoint anyone can call, memory that climbs until the pod restarts, or a webhook that charges twice. Every question here is a concrete scenario. The answers walk through the order of checks, say what to look at first, and name what would change your mind. This page is written for anyone facing an ASP.NET Core round built on scenarios like these. Practice by reading only the question, talking through your own plan out loud, then comparing it with the sample. For core concepts like middleware, DI lifetimes and change tracking, start with the main .NET page.

Search all questions by round, difficulty and level, or save the ones you want to practice.

Startup and Config 3 questions

Easy Technical round Fresher, Mid-level Practice question

1. You add a new OrderService, and the app now fails with 'Unable to resolve service for type IEmailSender while attempting to activate OrderService'. What do you check?

What the interviewer is really testing:
Whether you can read a DI error message literally and find the missing or mismatched registration without guessing.
Answer frame:

Read it: the container is building OrderService and has nothing registered for IEmailSender.

Look for the registration: is it missing, registered as the concrete class only, or inside an extension method nobody calls?

Guard it: keep scope and build validation on so a missing registration fails at startup, not on a live request.

Sample spoken answer:

“The message tells me exactly what's wrong: to build OrderService the container needs an IEmailSender, and nothing is registered for that interface. So I'd search Program.cs and any AddSomething extension methods for IEmailSender. Common causes are that it was never registered, that someone registered the concrete SmtpEmailSender but the constructor asks for the interface, or that the registration lives in an extension method in another project that Program.cs never calls. I'd also check it's the same IEmailSender, because two interfaces with the same name in different namespaces catch people out. The fix is one line, like AddScoped<IEmailSender, SmtpEmailSender>(). What would change my mind is if it's registered and still fails, because then I'd look at the namespace or at a second constructor the container is picking.”

Red flag to avoid:

Fixing it by creating the dependency with new inside OrderService instead of registering it.

They may ask next:
  • Why might this error show up at startup on your laptop but only on the first request in production?
  • How would you choose the lifetime for the email sender you register?
Say it in 60 seconds
Easy Technical round Fresher Practice question

2. Swagger works on your machine, but after the first deploy to the test server, the Swagger page returns 404. Nothing else is broken. Where do you look?

What the interviewer is really testing:
Whether you know how the hosting environment changes what Program.cs does, and think about whether the page should be public at all.
Answer frame:

First suspect: the template only maps Swagger inside if (app.Environment.IsDevelopment()).

Confirm the environment: if ASPNETCORE_ENVIRONMENT is not set, the app runs as Production.

Decide on purpose: turn it on for the test server if wanted, but keep it off or protected in real production.

Sample spoken answer:

“If only Swagger is missing, I'd bet on the environment check. The project template wraps the Swagger calls in an IsDevelopment check, and when ASPNETCORE_ENVIRONMENT isn't set on the server, ASP.NET Core assumes Production. So the code that maps Swagger simply never runs. I'd confirm by logging app.Environment.EnvironmentName at startup. Then it's a decision, not just a fix. For a test server, I'd set the environment to Staging and allow Swagger there, or read a config flag for it. For real production, I'd usually keep it off or put it behind authentication, because it hands anyone a map of the API. If the environment turned out to be Development already, I'd check whether a reverse proxy is sending the path somewhere else.”

Red flag to avoid:

Setting the server to Development to make Swagger appear, which also turns on detailed error pages for everyone.

They may ask next:
  • What else in a typical Program.cs behaves differently between Development and Production?
  • How would you keep API documentation available to partners without exposing it publicly?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

3. You set Payment:ApiKey as an environment variable on the Linux container, but the app keeps using the placeholder from appsettings.json. How do you work it out?

What the interviewer is really testing:
Whether you know how environment variable names map to configuration keys and how to see which source actually won.
Answer frame:

Name first: on Linux a colon in a variable name often isn't allowed; use a double underscore, Payment__ApiKey.

Is it there: confirm the variable exists in the running container, and the app was restarted after setting it.

Who wins: list the configuration sources and check nothing later, like command-line arguments, overrides it.

Sample spoken answer:

“Environment variables normally beat appsettings.json in the default setup, so either the variable isn't reaching the app or its name doesn't map to the key. My first suspect is the colon. Many Linux shells and container tools can't use a colon in a variable name, so .NET accepts a double underscore instead: Payment__ApiKey becomes Payment:ApiKey. Next I'd exec into the container and print the environment to see the variable really exists, and check the app was restarted, because it reads variables at startup. Then I'd check the code reads the same section name, spelled the same way. Locally, with the same variables set, I'd print the configuration debug view, which shows which provider supplied each value. What would change my mind is if the value is right in configuration but wrong in the class, because then something is caching it at startup.”

Red flag to avoid:

Putting the real key into appsettings.json and committing it to make the container work.

They may ask next:
  • Where would you keep a real API key instead of an environment variable in plain text?
  • Why should you never print the configuration debug view to production logs?
Say it in 60 seconds

Web APIs 5 questions

Easy Technical round Fresher, Mid-level Practice question

4. A POST endpoint gets called, but the order parameter is null or every property on it is empty. The frontend developer insists they are sending JSON. How do you debug it?

What the interviewer is really testing:
Whether you know where model binding reads a complex parameter from and the quiet ways JSON fails to land on your class.
Answer frame:

Binding source: without [ApiController], a complex parameter is not read from the body unless you add [FromBody].

Request: check the Content-Type header and the raw body the client really sends.

Shape: compare JSON names with the class, and check for public fields or non-public setters the serializer skips.

Sample spoken answer:

“I'd start with where the parameter is bound from. If the controller has [ApiController], a complex type comes from the body automatically. Without it, ASP.NET Core looks in the form and query string, so I'd need [FromBody]. Next I'd capture the actual request in the browser's network tab: is Content-Type application/json, and what's really in the body? If the object arrives but every property is empty, the names probably don't match. The default serializer ignores case, but customer_id still won't match CustomerId. It also skips public fields and properties with private setters unless you tell it otherwise. What would change my mind is getting a 400 or 415 instead of an empty object, because then the framework is rejecting the request, and the response body tells me why.”

Red flag to avoid:

Switching the parameter to a string and parsing JSON by hand instead of finding why binding fails.

They may ask next:
  • How would you accept snake_case JSON without renaming every C# property?
  • Why does [ApiController] return 400 for bad JSON when a plain controller just gives you null?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

5. A client gets a 400 with a validation error saying MiddleName is required. Nobody added a Required attribute, and your breakpoint in the action never hits. What is happening?

What the interviewer is really testing:
Whether you know about the automatic 400 from ApiController and how nullable reference types quietly make properties required.
Answer frame:

Who answered: [ApiController] returns a 400 problem response when model state is invalid, before the action runs.

Why required: with nullable reference types on, a non-nullable string property is treated as required.

Fix on purpose: make optional fields string?, and keep required the ones that truly are.

Sample spoken answer:

“The breakpoint never hitting tells me the framework answered, not my code. With [ApiController], if model validation fails, ASP.NET Core sends a 400 with a problem details body automatically and the action never runs. So the question is why MiddleName counts as required. My guess is nullable reference types are on, and the property is declared as string rather than string?. MVC treats a non-nullable reference type as if it had a Required attribute. The client left the field out, so validation failed. The fix is to decide what the contract should be: middle name is optional, so I'd make it string?. I'd read the errors object in the response, because it names each failing field. If the error was about converting a value instead, I'd look at the JSON types, like an enum sent as text.”

Red flag to avoid:

Turning off nullable reference types for the whole project to make one error go away.

They may ask next:
  • How would you customise the shape of that automatic 400 response for the whole API?
  • What are the risks of turning off the automatic validation response?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

6. Your React app gets a CORS error calling your API, but the same request works fine from Postman and curl. What do you check, in order?

What the interviewer is really testing:
Whether you understand that CORS is enforced by the browser, and can find the policy or ordering mistake on the server.
Answer frame:

Why only the browser: CORS is a browser rule; Postman and curl ignore it.

The preflight: look at the OPTIONS request and its response headers in the network tab.

Server setup: exact origin match, UseCors in the right place, and no AllowAnyOrigin with credentials.

Sample spoken answer:

“First, Postman working doesn't prove much, because CORS is enforced by the browser, not the server. So I'd open the browser's network tab. For a POST with JSON, the browser sends a preflight OPTIONS request first, and I'd check whether its response has the Access-Control-Allow-Origin header. If it doesn't, I'd check the policy: the origin must match exactly, scheme and port included, with no trailing slash. Then the order in Program.cs: UseCors has to come before authorization, or a rejected request, sometimes even the preflight, comes back without CORS headers. If the app sends cookies, AllowAnyOrigin won't work with AllowCredentials; I'd list the real origins. What would change my mind is if the failing request is actually a 500. An unhandled error can come back without the CORS headers, so the browser reports CORS when the real bug is in my code.”

Code:
builder.Services.AddCors(o => o.AddPolicy("web", p => p
    .WithOrigins("https://app.example.com")
    .AllowAnyHeader()
    .AllowAnyMethod()));

var app = builder.Build();
app.UseCors("web");
app.UseAuthentication();
app.UseAuthorization();
Red flag to avoid:

Allowing every origin with credentials in production just to make the error disappear.

They may ask next:
  • Why does a simple GET sometimes work when the POST fails?
  • Would you ever allow any origin, and for what kind of endpoint?
Say it in 60 seconds
Hard Case round Mid-level, Senior Practice question

7. Some customers are credited twice because the payment provider sends the same webhook more than once. Your handler does all the work before responding. How would you fix it?

What the interviewer is really testing:
Whether you design webhook handlers to be idempotent and fast, using a unique event record rather than hoping for exactly-once delivery.
Answer frame:

Why duplicates: providers retry when they don't get a quick success, and a slow handler invites retries.

Idempotency: store the event ID with a unique constraint; if the insert fails, it's a repeat, so return success.

Respond fast: verify the signature, record the event, answer 200, and process it in the background.

Guard the state: only apply a credit if the order isn't already paid.

Sample spoken answer:

“Providers deliver webhooks at least once, not exactly once, and ours makes it worse by doing slow work before replying, so the provider times out and retries while the first call is still running. I'd fix both sides. First, idempotency: when a webhook arrives, I verify the signature, then insert the event ID into a table with a unique constraint. If that insert fails on the constraint, I've already seen this event, so I return 200 and do nothing. Second, speed: after recording the event I respond straight away and hand the work to a background processor, ideally through an outbox so nothing is lost. Third, the business logic itself checks state, so it only credits an order that isn't already credited, inside a transaction. I'd also find and reverse the double credits already made.”

Red flag to avoid:

Adding a lock or a delay to the handler and assuming the provider will stop sending duplicates.

They may ask next:
  • Why is checking 'have I seen this ID' with a select before insert not enough?
  • How would you replay webhooks the provider sent while your API was down?
Say it in 60 seconds
Medium Situational round Fresher, Mid-level, Senior Practice question

8. A refactor renamed a C# property, the JSON field name changed with it, and older versions of the mobile app now crash. What do you do now, and what do you change for the future?

What the interviewer is really testing:
Whether you restore service first and then protect the API contract with explicit names, additive changes and checks in CI.
Answer frame:

Now: roll back, or send the old field name again, so old apps stop crashing.

Why it happened: the wire name was tied to the C# name, so a rename was a breaking change.

Future: pin names with [JsonPropertyName], make changes additive, and version the API for true breaks.

Catch it: compare the OpenAPI document or run contract tests in CI.

Sample spoken answer:

“First, stop the crashes. Old app versions stay installed for months, so I'd roll back or quickly ship a fix that sends the old field name again, possibly alongside the new one. Then I'd tell the mobile team what happened and when it's fixed. The root cause is that our JSON names came straight from C# property names, so a harmless-looking rename was a breaking change. For the future I'd pin wire names with JsonPropertyName on public contracts, so refactoring can't change them. I'd agree a rule that changes are additive: add new fields, keep old ones until usage drops, and use API versioning for real breaks. And I'd add a CI step that compares the generated OpenAPI document with the last release and fails on removed or renamed fields. What would change my mind is an internal-only API, where coordinated releases are simpler.”

Red flag to avoid:

Telling mobile users to update the app and leaving the API as it is.

They may ask next:
  • How would you know when it's safe to remove the old field?
  • Would you version by URL, header or query string, and why?
Say it in 60 seconds

Security 3 questions

Medium Technical round Mid-level, Senior Practice question

9. A penetration tester finds that a new admin endpoint answers without any token at all. The rest of the API is locked down. What do you check, and how do you stop it happening again?

What the interviewer is really testing:
Whether you know how authorization metadata is applied per endpoint and prefer a secure default over remembering an attribute.
Answer frame:

This endpoint: is [Authorize] or RequireAuthorization there at all, and is [AllowAnonymous] on it, its controller or a base class?

Contain: add authorization now and check logs for calls that already happened.

Default deny: set a fallback policy so every endpoint needs a signed-in user unless it opts out.

Sample spoken answer:

“First I'd look at how the endpoint got its protection, or didn't. In ASP.NET Core authorization is per endpoint, so a new controller without [Authorize], or a minimal API without RequireAuthorization, is open even if everything around it is locked. I'd also check for [AllowAnonymous] on the controller or a base class, because it overrides [Authorize] wherever it appears. I'd fix it straight away and search the access logs for calls to that route. The real lesson is that security shouldn't depend on remembering an attribute. I'd set a fallback authorization policy that requires an authenticated user, so any endpoint without its own rules is locked by default, and only login and health checks opt out. I'd also add a test that lists every endpoint and fails if one has no authorization rule.”

Code:
builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

app.MapGet("/health", () => Results.Ok()).AllowAnonymous();
Red flag to avoid:

Adding the attribute to that one endpoint and calling it done, with nothing to stop the next one.

They may ask next:
  • Does a signed-in user requirement alone protect an admin endpoint?
  • How would you write that test that checks every endpoint has an authorization rule?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

10. The decoded JWT clearly shows the user has the Admin role, but every call to an endpoint with Authorize Roles Admin returns 403. What do you check?

What the interviewer is really testing:
Whether you know 403 means the token was accepted, and can trace how a claim in the token becomes a role on the user.
Answer frame:

Read the code: 403 means authentication worked and authorization failed, so the token itself is fine.

See the claims: log the claim types and values the API actually built from the token.

Match them: point RoleClaimType at the claim that holds roles, and check spelling and case.

Sample spoken answer:

“A 403 rather than a 401 tells me the token was accepted and the problem is the role check. So I'd stop looking at signing keys and look at claims. In a development build I'd log every claim type and value on User for that request. Often the role is there but under a claim name the framework doesn't treat as a role, like roles, groups or a custom user_role, or claim mapping was switched off so the name stays short while the framework expects the long role claim type. The fix is to set RoleClaimType in the token validation parameters to the claim that really holds roles. I'd also check the exact value, since role names are compared exactly, so admin isn't Admin. What would change my mind is if the roles arrive as one comma-separated string, because then it needs splitting into separate claims.”

Red flag to avoid:

Regenerating tokens or changing signing keys when the status code already says authentication succeeded.

They may ask next:
  • When would you switch from role checks to a named policy?
  • How would you add roles from your own database after the token is validated?
Say it in 60 seconds
Medium Situational round Fresher, Mid-level, Senior Practice question

11. While looking into a bug, you notice full login request bodies, passwords included, in your API's production logs. What do you do?

What the interviewer is really testing:
Whether you treat it as a security incident first, then find the logging source and stop it recurring.
Answer frame:

Escalate: tell your lead or security contact now; it's an incident, not a cleanup task.

Stop the leak: find the source, like HTTP logging with request bodies, custom body logging, or EF sensitive data logging.

Clean up: restrict and purge the affected logs, and reset passwords if many people could read them.

Prevent: redact sensitive fields, log safe summaries instead of whole objects, and add a review rule.

Sample spoken answer:

“I'd stop the bug work and tell my lead or the security contact straight away, because passwords in logs is an incident, and other people may need to decide on resets. Then I'd find the source. Usually it's HTTP logging configured to include request bodies, a custom middleware that logs every body, logging the whole login object with structured logging, or EF Core's sensitive data logging left on, which logs parameter values. I'd switch that off with a config change and deploy. Then, with the security team, restrict access to the affected logs and purge them where we can, including copies in any log platform. If a lot of people could read those logs, forcing affected users to reset passwords is the safe call. Finally I'd add redaction for sensitive fields and a test that checks the login body never appears in logs.”

Red flag to avoid:

Quietly deleting the log lines and fixing the config without telling anyone.

They may ask next:
  • How would you decide whether affected users must reset their passwords?
  • What else, besides passwords, should never be written to logs?
Say it in 60 seconds

Dependency Injection 1 question

Hard Technical round Mid-level, Senior Practice question

12. A few customers report briefly seeing another customer's name and orders. It only happens under heavy traffic and you cannot reproduce it on your machine. Where do you start?

What the interviewer is really testing:
Whether you think of shared state across concurrent requests, like per-user data held in a singleton, static field or badly keyed cache.
Answer frame:

Treat it as an incident: it's a data leak, so escalate, and turn off any suspect cache first.

Hunt shared state: singletons and static fields holding the current user, or an HttpContext kept past its request.

Check caches: any cache key built from the URL alone for a response that depends on the user.

Prove it: a load test with two users that checks each response belongs to its caller.

Sample spoken answer:

“Only under load and only briefly sounds like shared state being overwritten between requests, so I'd treat it as a data leak and tell my lead straight away. Then I'd look for three things. First, anything registered as a singleton, or any static field, that stores the current user, like a CurrentUser service where middleware sets the user ID on each request. Two requests at once overwrite each other. Second, any code that keeps the HttpContext itself in a field instead of reading it per request. Third, caches: a response or data cache keyed only by URL will happily serve one customer's orders to the next. To prove it, I'd write a load test with two accounts that asserts every response belongs to the caller. The fix is making per-user state scoped, and putting the user ID in every cache key.”

Red flag to avoid:

Dismissing it because it can't be reproduced locally, or adding a lock around the controller.

They may ask next:
  • Why is reading the user through IHttpContextAccessor at call time safer than storing it in a field?
  • How would you tell customers and your team what happened?
Say it in 60 seconds

Performance and Memory 2 questions

Hard Technical round Mid-level, Senior Practice question

13. Your API's memory climbs steadily all day until the container is killed for running out of memory, then it starts over. How do you find the cause?

What the interviewer is really testing:
Whether you can tell a real leak from normal GC behaviour and use heap snapshots to find what is holding objects alive.
Answer frame:

Is it a leak: watch GC heap size and gen 2 over time with dotnet-counters; a real leak keeps rising after collections.

Compare snapshots: take two heap dumps hours apart and see which types grew and what roots them.

Usual suspects: unbounded static or singleton collections, caches without limits, event subscriptions, disposables resolved from the root provider.

Sample spoken answer:

“First I'd make sure it's a leak and not the server garbage collector being generous with memory. I'd run dotnet-counters against the container and watch the GC heap size and gen 2 size. If gen 2 keeps rising after collections, something is holding references. Then I'd take a gcdump early in the day and another a few hours later and compare which types grew, and follow the paths back to their roots. In APIs the answer is usually boring: a static dictionary used as a cache with no removal, IMemoryCache entries with no expiry or size limit, a singleton subscribed to events that never unsubscribes, or disposable transient services resolved from the root provider, which the container holds until the app shuts down. What would change my mind is if managed heap stays flat while process memory grows, because then I'd look at native memory or unreleased streams.”

Red flag to avoid:

Raising the memory limit or scheduling a nightly restart and calling the problem solved.

They may ask next:
  • How does the container memory limit interact with how much memory the GC will use?
  • Why do disposable transients resolved from the root provider stay alive?
Say it in 60 seconds
Medium Case round Mid-level, Senior Practice question

14. The product catalog endpoint is hammering the database, but prices change only a few times a day. The API runs on four instances. How would you add caching?

What the interviewer is really testing:
Whether you choose between in-memory, distributed and output caching based on staleness and invalidation, not habit.
Answer frame:

Ask first: how stale may a price be, and who must see a change immediately?

Options: IMemoryCache per instance, a shared distributed cache like Redis, or output caching of whole responses.

Invalidation: with four instances, clearing one memory cache leaves three stale copies.

Stampede: stop many requests rebuilding the same entry at once when it expires.

Sample spoken answer:

“My first question is how stale a price is allowed to be, because that decides the design. If a minute or two is fine, IMemoryCache on each instance with a short expiry is the simplest win. The catch is four instances: when an admin changes a price, clearing the cache on one instance leaves three with the old value until they expire. If prices must update at once, I'd use a shared cache like Redis through IDistributedCache, or output caching with a Redis store, and evict by tag when the admin saves. I'd make sure the cache key includes anything that changes the response, like currency or language. I'd also protect against a stampede, where the entry expires and hundreds of requests hit the database together. What would change my mind is if checkout reads these prices, because checkout should use the database price, not a cached one.”

Red flag to avoid:

Adding an in-memory cache with no expiry on four instances and assuming admin edits will show up.

They may ask next:
  • What would you do if Redis itself goes down?
  • How would you measure whether the cache is actually helping?
Say it in 60 seconds

EF Core 4 questions

Medium Technical round Mid-level, Senior Practice question

15. At peak hours, requests start failing with 'Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool'. What do you look at?

What the interviewer is really testing:
Whether you know pool exhaustion usually means connections are held too long or leaked, not that the pool is too small.
Answer frame:

Meaning: every pooled connection is in use and none came back in time.

Leaks: raw connections or DbContexts created without using, or long transactions left open.

Holding too long: slow queries, blocking calls, or outside HTTP calls made while a transaction is open.

Measure: count open sessions on the database side and match them to code paths.

Sample spoken answer:

“That error means all the connections in the pool are checked out and none were returned within the wait time. The default pool for SQL Server allows 100, so the question is why so many are busy. I'd first look for leaks: Dapper or ADO.NET code that opens a connection without a using, or a DbContext created with new somewhere and never disposed. Then for connections held too long, like slow queries, a transaction open while the code calls an outside API, or sync-over-async blocking that makes each request slower. On the database side I'd list the open sessions and what they're running, which usually points straight at the code path. What would change my mind is if every connection is busy with fast, healthy queries, because then it really is traffic, and I'd look at scaling or raising the pool size carefully.”

Red flag to avoid:

Setting Max Pool Size to a huge number and moving on without finding who holds the connections.

They may ask next:
  • Why is raising Max Pool Size usually the wrong first fix?
  • How does DbContext pooling differ from connection pooling?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

16. An orders list endpoint answers instantly in development, but in production, with a few million rows, it times out. The code has not changed. What do you check first?

What the interviewer is really testing:
Whether you look at the SQL EF Core really sends and spot filtering in memory, missing paging or missing indexes.
Answer frame:

See the SQL: log it or call ToQueryString() to check filters and limits reach the database.

Memory filtering: a ToList() or AsEnumerable() before Where pulls the whole table into the app.

Shape: page the results, select only needed columns, and index the columns you filter and sort on.

Sample spoken answer:

“Development has a few hundred rows, so anything works there. I'd start by looking at the SQL EF Core actually generates, from the logs or with ToQueryString(). The classic cause is a ToList() or AsEnumerable() too early, or a repository method returning IEnumerable, so the Where runs in memory after the whole table has been loaded. Next I'd check there's paging at all, because returning every order to one screen will never scale. Then I'd run the generated SQL against a production-sized copy and look at the plan: filtering by customer and sorting by date without an index on those columns means a scan. I'd also project to just the fields the list shows. What would change my mind is if the SQL is tight and indexed but still slow, because then I'd look at locking or at the database server itself.”

Code:
var rows = await db.Orders
    .Where(o => o.CustomerId == customerId)
    .OrderByDescending(o => o.CreatedAt)
    .Skip((page - 1) * pageSize)
    .Take(pageSize)
    .Select(o => new OrderRow(o.Id, o.CreatedAt, o.Total))
    .ToListAsync(ct);
Red flag to avoid:

Raising the command timeout so the query has more time to load the whole table.

They may ask next:
  • Why does Skip and Take get slow on deep pages, and what would you use instead?
  • How would you catch this kind of query before it reaches production?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

17. An admin uploads a CSV with 50,000 products, and the import endpoint takes twenty minutes and sometimes times out. The code calls Add and SaveChanges for each row. What would you change?

What the interviewer is really testing:
Whether you see the per-row round trips and growing change tracker, and pick batching or bulk tools based on size.
Answer frame:

Cause: one SaveChanges per row is one round trip per row, and the change tracker keeps growing.

Batch: add rows in chunks, save once per chunk, and clear the tracker between chunks.

Move it: for big files, run the import as a background job and report progress.

Bigger still: use the database's bulk load path when batching isn't enough.

Sample spoken answer:

“Calling SaveChanges fifty thousand times means fifty thousand round trips to the database, and every tracked entity stays in the change tracker, so each save gets a bit slower. I'd batch it: add a thousand rows at a time, call SaveChanges once per batch, which EF Core sends in a few batched commands, then clear the change tracker so it doesn't keep growing. I'd also decide what failure means. Should one bad row roll back everything, or just be reported? I'd validate all rows before writing anything. And an import this size shouldn't run inside an HTTP request, so I'd accept the file, queue a background job and let the admin see progress. What would change my mind is much larger files, where I'd use the database's bulk copy feature instead of EF Core.”

Code:
foreach (var chunk in rows.Chunk(1000))
{
    db.Products.AddRange(chunk.Select(ToEntity));
    await db.SaveChangesAsync(ct);
    db.ChangeTracker.Clear();
}
Red flag to avoid:

Only raising the request timeout so the twenty-minute request can finish.

They may ask next:
  • How would you handle rows that already exist in the table?
  • When would you wrap the whole import in one transaction, and when not?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

18. A teammate wants to wrap EF Core in a generic repository and unit of work for the whole new project, so 'we can swap the database and mock everything'. How do you respond?

What the interviewer is really testing:
Whether you can weigh an architecture proposal on the problem it solves, knowing DbContext already acts as a unit of work.
Answer frame:

Ask the goal: what problem is this solving: testing, swapping databases, or hiding EF Core?

What EF gives: DbContext is already a unit of work, and DbSet already works much like a repository.

Costs: a generic layer tends to hide Include, projections and async, or leaks IQueryable anyway.

Middle ground: focused repositories or query services with named methods, and integration tests on a real database.

Sample spoken answer:

“I'd start by asking what problem we're solving, because the two reasons given don't hold up well. DbContext already is a unit of work, since SaveChanges commits everything together, and a DbSet already behaves a lot like a repository. A generic IRepository<T> with Get, Add and Delete usually ends up either hiding the things that make EF Core fast, like projections, Include and split queries, or returning IQueryable, which means EF Core leaks through anyway. Swapping databases is rare, and EF Core's providers handle much of that. For testing, mocks of a repository don't test the SQL, so I'd rather run integration tests against a real database in a container. What I'd support is focused repositories or query classes with meaningful names, like GetOverdueInvoices, where they make the domain code clearer. I'd suggest trying that on one feature first.”

Red flag to avoid:

Either accepting the pattern because it's common or dismissing it without asking what problem it solves.

They may ask next:
  • When would a repository layer over EF Core genuinely earn its place?
  • How would you settle this if the team stays split after the discussion?
Say it in 60 seconds

Reliability 4 questions

Medium Technical round Mid-level, Senior Practice question

19. At 3 a.m. the whole API went down. The logs show an unhandled exception in your queue-consumer BackgroundService, then 'Application is shutting down'. What happened, and what do you change?

What the interviewer is really testing:
Whether you know an unhandled exception in a BackgroundService stops the host by default in modern .NET, and how to handle failures per item.
Answer frame:

What happened: since .NET 6, an exception escaping ExecuteAsync stops the whole host by default.

Per item: catch and log failures inside the loop so one bad message doesn't end the worker.

Bad messages: retry with a delay, then move repeat failures aside instead of retrying for ever.

Don't hide it: setting the behaviour to Ignore leaves a dead worker inside a live API.

Sample spoken answer:

“Since .NET 6, if an exception escapes a BackgroundService's ExecuteAsync, the host logs it and shuts down the whole app by default, API included. So one bad message took down everything. I'd first find which message caused it and why. Then I'd change the worker so each message is handled inside its own try and catch: log the error with the message ID, wait a moment, and carry on. A message that keeps failing should go to a dead-letter queue after a few tries, not loop for ever. Shutdown should still work, so I'd let cancellation from the stopping token end the loop cleanly. I wouldn't just switch the host to ignore worker exceptions, because then the worker dies silently while the API looks healthy. I'd also think about running the consumer as its own service.”

Code:
while (!stoppingToken.IsCancellationRequested)
{
    try
    {
        await ProcessNextMessageAsync(stoppingToken);
    }
    catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
    {
        break;
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Message processing failed");
        await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
    }
}
Red flag to avoid:

Wrapping everything in a catch that swallows errors without logging, or setting the host to ignore them.

They may ask next:
  • How would you alert someone if the worker stopped making progress but didn't crash?
  • What are the pros and cons of moving the consumer into its own process?
Say it in 60 seconds
Hard Case round Senior Practice question

20. On every deploy to Kubernetes, a handful of requests fail with errors and some queue messages get processed twice. How do you make deploys clean?

What the interviewer is really testing:
Whether you understand graceful shutdown end to end: traffic draining, the host shutdown timeout, stopping tokens and idempotent consumers.
Answer frame:

Traffic: make the pod stop getting new requests before it stops serving; readiness fails first, a short pre-stop delay helps.

Host: on SIGTERM Kestrel finishes in-flight requests up to the shutdown timeout; keep it shorter than the pod's grace period.

Workers: honour the stopping token and acknowledge a message only after it's fully processed.

Assume duplicates: make message handlers idempotent anyway.

Sample spoken answer:

“Two different problems share one cause: the app stops before its work is done. For failed requests, I'd check the order of events. When a pod is terminated, the load balancer may still route to it for a few seconds, so I'd add a short pre-stop delay and make readiness fail first. Then ASP.NET Core gets SIGTERM, stops accepting connections and waits for in-flight requests up to the host's shutdown timeout. That timeout must fit inside the pod's grace period, or Kubernetes kills it mid-request. For duplicate messages, I'd look at when the worker acknowledges. If it's processing when the stopping token fires and gets killed before acking, the broker redelivers. So workers should stop taking new messages on cancellation and finish the current one. And since redelivery can always happen, I'd make handlers idempotent.”

Red flag to avoid:

Blaming the deploy tool and accepting a few errors per release as normal.

They may ask next:
  • How would you test graceful shutdown before the next deploy?
  • What changes if a single request can legitimately take several minutes?
Say it in 60 seconds
Medium Case round Mid-level, Senior Practice question

21. When the database gets slow, Kubernetes starts restarting all your API pods, which makes the outage worse. Your health endpoint checks the database. What would you change?

What the interviewer is really testing:
Whether you know the difference between liveness and readiness and keep dependency checks out of liveness.
Answer frame:

Why it cascades: a liveness probe that checks the database restarts healthy pods for someone else's problem.

Split them: liveness only says the process can answer; readiness includes the database.

Tag checks: use health check tags and a predicate per endpoint.

Sample spoken answer:

“The pods aren't broken; the database is slow. But because the liveness probe calls the database, Kubernetes thinks every pod is dead and restarts them all, so we get cold starts and a wave of reconnects on top of a struggling database. I'd split the health checks. Liveness should only prove the process can answer a request, with no outside dependencies. Readiness can include the database, so a pod that can't reach it is taken out of the load balancer but not killed. In ASP.NET Core I'd tag the database check as ready and map two endpoints with different predicates. I'd also check the probe timeouts, because a very short timeout under load looks like a failure. What would change my mind is if the pods really do hang, because then liveness failing is doing its job.”

Code:
builder.Services.AddHealthChecks()
    .AddCheck<DatabaseHealthCheck>("database", tags: new[] { "ready" });

app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false });
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});
Red flag to avoid:

Just making the liveness timeout longer while it still depends on the database.

They may ask next:
  • Should readiness fail if an optional service, like email, is down?
  • What would a startup probe add here?
Say it in 60 seconds
Hard Case round Mid-level, Senior Practice question

22. A partner's pricing API sometimes hangs, and while it does, requests to your checkout endpoint pile up and time out for every customer. How would you protect your API?

What the interviewer is really testing:
Whether you set real timeouts, pass cancellation through, and use circuit breakers and fallbacks without unsafe retries.
Answer frame:

Timeouts: HttpClient waits 100 seconds by default; set one that matches what checkout can afford.

Cancellation: pass the request's cancellation token down so abandoned requests stop work.

Fail fast: a circuit breaker stops calling a partner that keeps failing.

Degrade: decide the fallback, like a cached price or a clear error, and retry only safe calls.

Sample spoken answer:

“First I'd check the timeout, because HttpClient waits 100 seconds by default, far longer than any customer will. I'd set a timeout based on what checkout can afford, a few seconds at most. I'd pass the request's cancellation token into the call, so if the customer gives up, we stop waiting too. Then I'd add resilience through HttpClientFactory: a per-attempt timeout, a circuit breaker so that after repeated failures we stop calling the partner for a short time and fail fast, and retries only for calls that are safe to repeat. Getting a price is safe to retry. Anything that charges money needs an idempotency key first. Finally I'd agree the fallback with the product owner, like using a recently cached price or showing a clear message. What would change my mind is a partner with a strict contract, where we'd follow their retry rules.”

Red flag to avoid:

Adding retries with no timeout or circuit breaker, which multiplies the load on a partner that is already struggling.

They may ask next:
  • How would you stop one slow partner from using up resources for unrelated endpoints?
  • How would you know the circuit breaker opened in production?
Say it in 60 seconds

Hosting and Deployment 2 questions

Medium Technical round Mid-level Practice question

23. Users uploading 200 MB videos get a 413 error, and when you test locally, the server's memory jumps during uploads. How do you handle large uploads?

What the interviewer is really testing:
Whether you know every layer has its own body size limit and that big files should be streamed, not buffered.
Answer frame:

Who said 413: Kestrel, IIS and a proxy like Nginx each have their own request size limit.

Raise it narrowly: lift the limit only on the upload endpoint, not for the whole app.

Don't buffer: stream the body to storage, or let the client upload straight to object storage with a short-lived link.

Sample spoken answer:

“First I'd find out which layer returned the 413. Kestrel's default body limit is about 30 MB, IIS has its own, and Nginx in front has a small default, so the response headers or body usually tell me who answered. Then I'd raise the limit only on the upload endpoint with the RequestSizeLimit attribute, plus the form's multipart limit if needed, and match it on the proxy. For memory, IFormFile buffers the whole upload before my code runs, and copying it into a byte array or MemoryStream holds the whole video in memory. I'd stream the request body straight to storage instead. For files this big, my preferred design is letting the browser upload directly to object storage using a short-lived signed URL, so the API never carries the bytes. What would change my mind is if files must be scanned or processed first, which changes where that work happens.”

Red flag to avoid:

Removing the request size limit for the whole app and reading every upload into a byte array.

They may ask next:
  • How would you let users resume an upload that fails halfway?
  • How would you check the file is really a video and not something harmful?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

24. After moving the API behind a load balancer that ends HTTPS, login redirects go to http URLs, and every logged client IP is the load balancer's. What is missing?

What the interviewer is really testing:
Whether you know the app sees the proxy's connection and needs forwarded headers, trusted only from known proxies.
Answer frame:

Why: the app sees a plain HTTP connection from the proxy, so scheme and IP are the proxy's.

Fix: use the forwarded headers middleware for X-Forwarded-For and X-Forwarded-Proto, early in the pipeline.

Trust: only accept those headers from your proxy's addresses, or clients can fake their IP.

Sample spoken answer:

“The load balancer ends HTTPS and talks plain HTTP to the app, so the app thinks every request is HTTP and comes from the load balancer. That's why redirect URLs are built with http and the logged IP is wrong. The proxy sends the original details in X-Forwarded-Proto and X-Forwarded-For headers, and the forwarded headers middleware applies them. It has to run early, before authentication, HTTPS redirection and logging, so they see the corrected values. By default it only trusts proxies on the local machine, so I'd add the load balancer's address range as a known network. I wouldn't trust the headers from anywhere, because then any client could claim any IP and dodge IP-based limits. What would change my mind is if the headers never arrive, which I'd check by logging raw request headers once in a test environment.”

Code:
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    // also add the load balancer's address range as a known network
});

var app = builder.Build();
app.UseForwardedHeaders();
Red flag to avoid:

Hard-coding https into redirect URLs instead of fixing how the app reads the original request.

They may ask next:
  • What happens when there are two proxies in a row?
  • Why can HTTPS redirection loop forever when forwarded headers are missing?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

You practiced these. On the real call, ClapAssist helps with the rest.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your live interview audio and screen are never stored. Your resume and notes are saved to your account so the app fills them in on any computer. It stays out of screen share on every plan, including Free; only you can see it.

Download with 10 free minutes
Mac and Windows · Stays out of screen share · No card