Skip to main content

Web API Interview Questions

Every question has a short model answer and a link to the lesson example that shows it in code. Levels: F = Fresher (0-2 yrs), M = Mid (3-5 yrs), S = Senior (6-9 yrs), A = Architect / Lead (10+ yrs).

HTTP basics

FWhat are a client and a server?concept

A client is a program that sends requests, such as a browser or a mobile app. A server is a program that waits for requests and answers them, such as our ASP.NET Core API. In the SMS, the teacher's browser is the client and the API is the server.

FWhat is an API?concept

An API (Application Programming Interface) is a set of addresses that other programs call to read or change data. A Web API works over HTTP and usually exchanges JSON. For example, GET /api/classes/10/sections/A/students returns the students of Class 10-A.

FWhat do the HTTP status codes 200, 201, 400, 404 and 500 mean?concept

200 OK means the request worked. 201 Created means something new was saved. 400 Bad Request means the client sent invalid data, 404 Not Found means the item does not exist, and 500 Internal Server Error means the server failed. Codes starting with 2 are success, 4 are client errors, 5 are server errors.

MWhat is the difference between GET and POST?concept

GET reads data, has no body, and is safe to repeat; browsers may cache or bookmark it. POST creates something new and sends data in the body; repeating it can create duplicates. Never change data with GET, because a refresh or a link preview could repeat it.

MWhat does it mean that HTTP is stateless, and how does the server know who is calling?concept

Stateless means each request stands alone; the server does not remember earlier requests. So every request must carry the caller's identity, usually a token in the Authorization header or a cookie. The API validates that token on every request.

MWalk me through what happens when a teacher opens the class list.concept

The browser sends a GET request with the class filter and the login token. The API checks the token and the teacher's permission, runs the C# logic, and queries the database for the enrolled students. SQL Server returns the rows, the API converts them to JSON with status 200, and the browser displays the list.

HTTP and REST

FWhat are the parts of an HTTP request and an HTTP response?concept

A request has a method and URL, headers, and an optional body. A response has a status line with a status code, headers, and an optional body. For example, POST /api/students with a JSON body got 201 Created, a Location header, and the new student as JSON.

FWhat is the difference between 400 and 409, and between 401 and 403?concept

400 Bad Request means the request itself is wrong, such as an empty name; sending it again unchanged fails again. 409 Conflict means the request is valid but clashes with the current state, such as issuing a book with no free copy. 401 means "not signed in"; 403 means "signed in, but not allowed".

FWhat is a query string, and when do you use it instead of a path segment?concept

The part of a URL after ?, with name=value pairs joined by &. Use the path to identify a resource, such as /api/students/3, and the query string for options on it, such as paging, sorting, or filtering: /api/students?page=2&pageSize=20.

MWhat do safe and idempotent mean for HTTP methods?concept

Safe methods change nothing on the server, such as GET. Idempotent methods have the same effect whether sent once or many times: GET, PUT, and DELETE are; POST is not. Deleting student 4 twice returned 204 then 404, but student 4 was gone either way.

MWhat is the difference between PUT and PATCH?concept

PUT replaces the whole resource with the body sent, so missing fields are treated as cleared, and sending it twice gives the same result. PATCH changes only the fields sent; whether it is idempotent depends on the change, such as "add 1" versus "set to 5".

MWhat makes an API RESTful? Give examples of good and bad paths.design

Resources with their own addresses, nouns in paths and verbs in methods, plural collections, children under parents, statelessness, and status codes that report the result. Good: GET /api/students/3/fee-invoices. Bad: POST /api/getFeeInvoicesForStudent?id=3.

SA mobile app shows success for failed fee payments. The API returns 200 with an error message in the body. What do you change?scenario

Return status codes that match the result: 400 for invalid input, 404 for a missing invoice, 409 for a conflict such as an already-paid invoice, and 2xx only for real success. Put details in a standard error body. Clients that check only the status code then behave correctly without changes.

How to answer:

Explain why status codes are the contract every client and tool reads, map each failure to a code, and mention a standard error format (ProblemDetails, Lesson 74) and versioning if clients depend on the old behaviour.

SA parent double-clicks 'Pay'. How do you stop two payments when POST is not idempotent?design

Make the operation idempotent: the client sends a unique idempotency key with the request, and the server stores it with the payment and returns the first result for any repeat with the same key. Disable the button after the first click too, but never rely on the client alone.

How to answer:

Name the problem (non-idempotent POST), describe idempotency keys with a unique constraint in the database, and add the UI and concurrency safeguards as extra layers.

AWhy does statelessness matter when an API must serve many schools and users?design

When each request carries everything needed, such as identity in a token and the ids in the URL, any server can handle any request. You can add servers behind a load balancer, restart one without losing sessions, and cache responses. Server-side session state ties users to one machine and limits scaling.

How to answer:

Connect statelessness to horizontal scaling, resilience, and caching, and say where state goes instead: the client, tokens, the database, or a distributed cache.

ASP.NET Core project

FWhat is Kestrel?concept

The cross-platform web server built into ASP.NET Core. It runs inside your application, so dotnet run alone can serve HTTP requests. In production it can face the internet directly or sit behind a reverse proxy such as IIS, Nginx, or a cloud load balancer.

FWhat are the two halves of Program.cs in an ASP.NET Core app?concept

Before builder.Build(), services are registered with builder.Services.Add..., such as controllers and OpenAPI. After it, the request pipeline is built with app.Use... and app.Map..., and app.Run() starts the server. Services cannot be added after Build().

FWhat is a controller, and what is an action?concept

A controller is a class, usually deriving from ControllerBase, that handles requests for one resource, with a route such as [Route("api/school")]. An action is a public method in it with an HTTP attribute such as [HttpGet]; it runs for the matching method and path.

MWhat is launchSettings.json for, and is it used in production?concept

It holds launch profiles for running the app on a developer's computer: URLs and ports, and environment variables such as ASPNETCORE_ENVIRONMENT=Development. It is not used in production; servers set the environment and URLs through their own configuration.

MHow does the hosting environment change an app's behaviour, and what is the default?concept

ASPNETCORE_ENVIRONMENT sets it, and code checks it with app.Environment.IsDevelopment(), to show tools such as the OpenAPI document only while developing; settings files can differ per environment too. When it is not set, the environment is Production, so development features stay off by default.

MWhy does an app log 'Failed to determine the https port for redirect'?scenario

UseHttpsRedirection wants to send HTTP requests to HTTPS, but the app has no HTTPS endpoint, for example when started with the http launch profile. It lets the request through and logs the warning. With an HTTPS endpoint configured, HTTP requests get a 307 redirect instead.

SEvery request to a new API returns 404, though the controller and route look correct. What do you check?scenario

That AddControllers() registers controllers and MapControllers() is in the pipeline, that the controller is public with [ApiController] and a [Route], that the action has the right HTTP attribute, and that the URL, including the api/ prefix and port, matches. Logging at Information level for routing shows which endpoint, if any, matched.

How to answer:

Work from the outside in: the right app and port, the pipeline, the registration, then the route template. Name the logging you would switch on to confirm.

SHow would you structure a Web API solution so the API project stays thin?design

The API project holds only HTTP concerns: controllers, middleware, configuration, and the composition root. Business rules and entities live in a core project, and data access in a data project, both referenced by the API. Controllers translate HTTP to service calls and results back to HTTP.

How to answer:

Describe the projects and their references, what belongs in each, and why it keeps rules testable without HTTP. Relate it to the layers from the repository pattern lesson.

AWhat decides how an ASP.NET Core API is hosted in production, and what must differ from development?design

The hosting model: Kestrel directly, behind a reverse proxy, in a container, or on a managed platform, chosen for TLS termination, scaling, and operations needs. In production the environment is Production, development tools such as OpenAPI UIs are off or protected, HTTPS is enforced, URLs come from server configuration, and secrets come from a secure store, never source files.

How to answer:

Cover hosting options with trade-offs, then a checklist of what changes between environments, and how configuration is supplied per environment.

Middleware

FWhat is middleware in ASP.NET Core?concept

A step in the request pipeline. Each middleware receives the HttpContext and the next step; it can run code before calling next, stop the request, or run code after the rest of the pipeline returns. Logging, HTTPS redirection, authentication, and error handling are all middleware.

FWhat is the difference between app.Use and app.Run(delegate)?concept

app.Use adds a middleware that receives the next step and normally calls it. app.Run with a delegate adds a terminal step that never calls a next one. app.Map starts a separate branch for a path prefix. (app.Run() with no arguments, at the end of Program.cs, starts the server.)

FWhat does short-circuiting mean in the middleware pipeline?concept

A middleware writes the response itself and does not call the next step, so the rest of the pipeline, including the controller, never runs. The earlier middleware still run their "after" code. Authentication answering 401, or a stock-taking check answering 503, are examples.

MHow do you write a custom middleware class?coding

A class whose constructor takes RequestDelegate next (plus singleton services such as a logger) and which has public async Task InvokeAsync(HttpContext context) that calls await _next(context). Register it with app.UseMiddleware<T>(), usually through an extension method such as app.UseRequestTiming().

MWhy can't a middleware add a header after calling next, and what do you do instead?coding

Once the action starts writing the body, the headers have been sent, and changing them throws "Headers are read-only, response has already started". Set headers before calling next, or register context.Response.OnStarting(...), which runs just before the headers go out.

MWhat is the usual order of the built-in middleware, and why?concept

Exception handling first, so it wraps everything; then HTTPS redirection, static files, routing and CORS, authentication, authorization, and finally the endpoints. Each step needs what the earlier ones did: authorization needs the user that authentication found.

SAfter a cleanup of Program.cs, every signed-in user gets 401. What would you look at first?scenario

The middleware order: UseAuthorization() placed before UseAuthentication() checks requests before any token has been read, so every caller looks anonymous. Restore the order, document why each middleware sits where it does, and add a test that signs in and calls a protected endpoint.

How to answer:

Go from symptom to the pipeline, explain the dependency between the two steps, and add the regression test so the order is protected.

SIn an app built with WebApplication, does it matter where MapControllers() appears relative to app.Use calls?concept

Not for when the endpoints run: MapControllers() only registers them, and WebApplication runs the endpoint middleware at the end of the pipeline, after every Use... call. A middleware added after MapControllers() still runs before the controller. The order of the Use... calls among themselves still matters.

How to answer:

Explain the implicit routing and endpoint middleware WebApplication adds, describe how you would verify it, and note that keeping Map calls last is still the readable convention.

AHow do you decide between middleware, filters, and code in the action for a cross-cutting concern?design

Middleware for concerns that apply to every HTTP request, even unmatched ones: timing, error handling, security headers, rate limiting. Filters for concerns tied to actions and their metadata: validation, per-action caching, action-level authorization. The action only for logic specific to that endpoint. Keep business rules out of all three, in the core layer.

How to answer:

Give the scope each mechanism sees, a concrete example for each, and the cost of putting a concern at the wrong level, such as running database work for every 404.

Controllers and minimal APIs

FWhat is the difference between a controller and a minimal API endpoint?concept

A controller is a class deriving from ControllerBase, with action methods routed by attributes and services injected through its constructor. A minimal API endpoint is a function mapped in code with app.MapGet and similar, receiving services as parameters. Both run on the same pipeline.

FWhat is a route parameter, and how does it reach the action?concept

A part of the route template in braces, such as {admissionNumber} in api/students/{admissionNumber}. ASP.NET Core takes that part of the URL and passes it to the action parameter with the same name, converting the type if needed.

FWhat does 405 Method Not Allowed mean, and how is it different from 404?concept

405 means the path exists but not for that HTTP method, such as POST to a GET-only route; the Allow header lists the methods that work. 404 means no route matches the path at all.

MWhat does the [ApiController] attribute do?concept

It makes attribute routing required, returns 400 automatically when model validation fails, applies binding source rules (complex types from the body), and turns error results such as NotFound() into ProblemDetails JSON. Without it, a controller behaves like an MVC controller.

MWhy return ActionResult<T> instead of IActionResult?coding

ActionResult<T> lets the action return either a T, which becomes 200 OK, or another result such as NotFound(), with less code. It also keeps the success type visible to tools, so the OpenAPI document describes the response body. IActionResult loses that type information.

MWhy did the controller and the minimal API return different 404 responses for the same missing student?scenario

[ApiController] converts NotFound() into a ProblemDetails body with type, title, status, and traceId, while the minimal API's Results.NotFound() sends an empty body. To make errors consistent, register ProblemDetails for the whole app so every error uses the same shape.

SWhat belongs in a controller action, and what does not?design

The action reads the request, calls a service, and turns the result into an HTTP response, such as 200 with a DTO or 404. Business rules, SQL, and entity mapping do not belong there: they go in the core services and the data layer, where they can be tested without HTTP.

How to answer:

Describe thin controllers and the reasons: testability, reuse across endpoints and other hosts, and keeping HTTP concerns in one layer. Give a concrete example of a rule that would be duplicated.

SA minimal API project's Program.cs has grown to 2,000 lines. How do you restructure it?scenario

Move each resource's endpoints into its own static class with an extension method such as MapStudentEndpoints(this IEndpointRouteBuilder app), using MapGroup("/api/students") for the shared prefix and settings. Move handler bodies into named methods so they can be tested. Program.cs then only calls one Map method per resource.

How to answer:

Give the structure, the grouping feature, and how it improves merges and tests; note that switching to controllers is not required to fix it.

AHow would you choose between controllers and minimal APIs for a new product, and how would you keep the choice reversible?design

Weigh the number of endpoints, team experience, the need for filters and conventions, and existing code. Controllers suit large APIs and teams used to them; minimal APIs suit small focused services. Keep endpoints thin and rules in services, use one error format and one validation approach, and the endpoint layer can be rewritten without touching the rest.

How to answer:

Make it a decision with criteria and a written record, and show that a layered design limits the cost of changing it later.

Dependency injection

FWhat is dependency injection?concept

A technique where an object receives the services it needs, usually through its constructor, instead of creating them itself. In ASP.NET Core a built-in container creates them: StudentsController asks for a StudentService and gets one, with its repository and DbContext built for it.

FWhat is the difference between singleton, scoped, and transient lifetimes?concept

Singleton: one object for the whole application. Scoped: one object per request, shared inside it. Transient: a new object every time one is requested. In the demo, the singleton id never changed, the scoped id changed per request, and transient ids differed even within one request.

FWhich lifetime should a DbContext have, and why?concept

Scoped, which AddDbContext uses by default. A DbContext tracks the entities of one unit of work and is not thread-safe, so each request needs its own, shared by the repositories and services handling that request, and disposed when the request ends.

MWhat is a captive dependency, and how does ASP.NET Core help you catch it?concept

A shorter-lived service held by a longer-lived one, such as a singleton whose constructor takes a scoped DbContext: the context then lives forever and is shared by every request. In Development, scope validation stops the app at startup with "Cannot consume scoped service ... from singleton". It is off in Production by default.

MWhy is the 'Unable to resolve service' error for a controller found only at request time?scenario

Controllers are not registered in the container; ASP.NET Core creates them when a request needs one, resolving their constructor parameters then. So startup validation does not see that a controller's dependency is missing. Tests that call every endpoint catch it before users do.

MWhy prefer constructor injection over resolving services from IServiceProvider inside methods?concept

Constructor injection makes every dependency visible in one place, lets the container validate them, and lets tests pass fakes directly. Resolving from IServiceProvider or HttpContext.RequestServices (the service locator pattern) hides dependencies and couples code to the container.

SParents occasionally see another parent's data at busy times. You suspect dependency injection. What do you look for?scenario

Singletons that hold per-request state, such as a current-user helper or a cache keyed by nothing, and singletons that capture scoped services. Check registrations for state and lifetimes, make per-request state scoped, keep singletons stateless or thread-safe, and add a concurrency test that runs two users in parallel.

How to answer:

Explain why the bug appears only under concurrency, list the suspicious registration patterns, and give the fix and a way to prove it.

SA singleton background service needs to read the database. How do you give it a DbContext safely?coding

Inject IServiceScopeFactory, create a scope for each piece of work with CreateAsyncScope(), resolve the DbContext or repository from that scope, and dispose the scope when the work ends. Each run then gets a fresh context, as a request would, instead of one captive context for the life of the app.

How to answer:

Name the captive dependency risk, show the scope-per-unit-of-work pattern, and mention IDbContextFactory as an alternative when only a context is needed.

AHow would you organise service registration in a solution with many projects?design

Each project registers its own types through one extension method, such as AddSchoolData(connection), so the host's Program.cs composes projects, not individual classes. Register interfaces to implementations, choose lifetimes deliberately, keep scope validation on in development and CI, and test that every endpoint resolves.

How to answer:

Cover ownership of registrations, the composition root, how lifetimes are reviewed, and automated checks. Mention keeping Core free of container packages if it should stay framework-neutral.

Configuration

FWhere does an ASP.NET Core app read its configuration from, and in what order?concept

By default: appsettings.json, then appsettings.{Environment}.json, then user secrets in Development, then environment variables, then command-line arguments. A later source overrides an earlier one for the same key.

FHow do you read a connection string in ASP.NET Core?coding

Put it under ConnectionStrings in configuration, such as ConnectionStrings:SchoolDb, and read it with builder.Configuration.GetConnectionString("SchoolDb"). Check that it is not null or empty at startup, and keep passwords out of committed files.

FWhat are user secrets, and when should you use them?concept

A per-user JSON file outside the project folder, linked by the project's UserSecretsId, for local development secrets such as your own connection string. They never reach Git and load only in the Development environment. They are not encrypted and not for servers.

MHow do you set a nested configuration key with an environment variable?coding

Replace each : with a double underscore: School__CurrentAcademicYear=2027-28 sets School:CurrentAcademicYear. The colon is not valid in environment variable names on every operating system; on the command line, --School:Name=... keeps the colon.

MWhat is the options pattern, and why use it instead of reading IConfiguration directly?concept

It binds a configuration section to a class, such as SchoolOptions, registered with AddOptions<T>().BindConfiguration("School") and injected as IOptions<T>. It gives typed values, one place for defaults and validation, and classes that do not depend on configuration keys.

MWhat is the difference between IOptions, IOptionsSnapshot, and IOptionsMonitor?concept

IOptions<T> reads the value once for the app's life. IOptionsSnapshot<T> is scoped and reads a fresh value each request, so file changes appear without a restart. IOptionsMonitor<T> is a singleton with the current value and change notifications, for long-lived services.

SA deployment used a wrong page size setting and broke a screen two days later. How do you catch such errors earlier?scenario

Bind settings to options classes with validation attributes, and call ValidateDataAnnotations() and ValidateOnStart(). The app then refuses to start with a message naming the setting, so the deployment fails at once and the previous version keeps serving. Add custom Validate rules for checks attributes cannot express.

How to answer:

Explain fail-fast configuration, how it interacts with deployment health checks, and give an example of the message an operator would see.

SA developer committed a production connection string with a password to Git. What do you do?scenario

Treat it as leaked: change the password immediately, check access logs, and only then remove it from the history if needed. Then fix the cause: production secrets come from environment variables or a secret store, local ones from user secrets, and a secret scanner runs on every push.

How to answer:

Order matters: rotate first, then clean up, then prevent. Mention that deleting the line does not remove it from history or from clones.

AHow would you design configuration and secret management for an API deployed to several environments?design

One build for all environments; safe defaults in appsettings.json; environment differences in environment files or host settings; secrets in a managed secret store, injected at startup with managed identities rather than stored passwords; options classes validated on start; and settings changes reviewed and versioned like code.

How to answer:

Cover the build-once principle, where each kind of value lives, how secrets are accessed without credentials, validation, and auditing changes.

OpenAPI

FWhat is OpenAPI, and how is it related to Swagger?concept

OpenAPI is a standard format, in JSON or YAML, that describes an HTTP API: paths, methods, parameters, bodies, responses, and data shapes. It was originally called the Swagger specification; Swagger is now the name of a family of tools around it, such as Swagger UI.

FWho uses an API's OpenAPI document?concept

Front-end and mobile developers to learn routes and fields, code generators to create typed clients, testers and test tools to know every status code, API gateways to import routes, and the API team itself as a reference and contract.

FHow do you add descriptions to an ASP.NET Core API's OpenAPI document?coding

Write /// XML comments on actions, with <summary>, <param>, and <response>, and set GenerateDocumentationFile to true in the project file. In .NET 10, the OpenAPI package reads the generated XML file and adds the text to the document.

MWhat does [ProducesResponseType] do, and what is the common mistake with it?coding

It declares a status code, and optionally a body type, that an action can return, so the OpenAPI document lists it. Once any response is declared, the success response is no longer inferred: declaring only 404 removed the 200. Declare every response, including the success one.

MHow do you change the document's title, or hide an endpoint from it?coding

Register a document transformer in AddOpenApi(options => options.AddDocumentTransformer(...)) and set document.Info.Title. Hide a controller or action with [ApiExplorerSettings(IgnoreApi = true)]; hiding does not protect it, so secure the endpoint separately.

MWhy does an integer field appear as type integer or string in an ASP.NET Core OpenAPI document?concept

ASP.NET Core's web JSON defaults allow reading numbers from strings, such as "3", so the schema honestly lists both types with a number pattern. Clients normally send numbers; change the JSON number handling if a client generator copes badly with it.

SWhich API changes are breaking for clients, and how do you catch them before release?design

Breaking: removing or renaming a response field, changing a field's type, adding a required request field, removing an endpoint or changing its meaning. Usually safe: adding endpoints or optional fields. Generate the OpenAPI document in the build and compare it with the previous version in every pull request, failing the build on breaking differences.

How to answer:

Give examples on both sides, then describe automated contract comparison and what you do when a breaking change is unavoidable (versioning, deprecation period).

SShould the OpenAPI document and a documentation page be available in production?design

Only as a deliberate decision. Public APIs often publish them, sometimes behind authentication; internal APIs may expose them only inside the network or not at all. Never expose them by accident, and never rely on hiding endpoints from the document as security.

How to answer:

Weigh discoverability for partners against exposing the attack surface, and describe controls: environment checks, authentication, or publishing a static copy of the document instead.

AHow would you use OpenAPI to keep a mobile app and an API in step across several teams?design

Treat the document as the contract: generate it from the API in CI, publish each version, generate the mobile client from it, and compare versions to detect breaking changes. Require complete response declarations and descriptions in review, and version the API when a break is necessary.

How to answer:

Cover generation, publication, client generation, contract checks, and versioning, and the team agreements that make it work, such as reviewing document diffs.

Routing

FWhat is a route template?concept

A pattern for request paths, made of literal segments and parameters in braces, such as api/students/{admissionNumber}. Routing matches the request's path and method against the templates and passes parameter values to the action.

FHow do a controller's [Route] and an action's [HttpGet] combine?concept

The controller's route is a prefix and the action's template is added to it. [Route("api/students")] with [HttpGet("{admissionNumber}")] gives GET api/students/{admissionNumber}; [HttpGet] alone gives GET api/students.

FWhen should a value go in the path, and when in the query string?design

In the path when it identifies the resource or its place in a hierarchy, such as an admission number or a class and section. In the query string when it is an option on a list: paging, filtering, or sorting, such as ?page=2&pageSize=20.

MWhat are route constraints, and what happens when one fails?concept

Rules on a route parameter, such as int, range(1,10), or length(1,2), that decide whether the route matches. When one fails, the route does not match, and the client gets 404 with no explanation, before any action runs. They are for matching, not for validation.

MHow do you read and validate optional query string values in a controller?coding

Add parameters with [FromQuery] and default values, such as int page = 1, and put validation attributes on them, such as [Range(1, 100)]. With [ApiController], an invalid value returns 400 ValidationProblemDetails naming the parameter, and the action never runs.

MWhat happens when two routes could match the same request?concept

The more specific template wins: a literal over a parameter, a constrained parameter over an unconstrained one, so /api/students/3 chose {id:int} over {admissionNumber}. If two are equally specific, the request fails with an ambiguous match error (500), which must be fixed in the routes.

SA list endpoint returns all 9,000 students and the mobile app is slow. How do you redesign it?scenario

Add paging with page and pageSize in the query string, a maximum page size, a stable sort ending with the key, and a response that includes the total count. Add filters such as sectionId so clients ask only for what the screen shows, and project to DTOs with only the needed fields.

How to answer:

Cover the request shape, the response shape, limits, sorting stability, filtering, and the database side (indexes, projection). Mention keeping old clients working while they move to the paged version.

SHow would you design routes for students, their guardians, and their fee invoices?design

/api/students for the collection and /api/students/{id} for one student; /api/students/{id}/guardians and /api/students/{id}/fee-invoices for related collections; and /api/fee-invoices/{invoiceNumber} for an invoice by its own key. Keep nesting to two parent levels, use plural lowercase nouns with hyphens, and put filters in the query string.

How to answer:

Explain the reasoning for each choice: ownership, unique keys, depth limits, and consistency, and how the routes appear in the OpenAPI document.

AOffset paging with page numbers slows down on very deep pages. What alternative would you consider?design

Keyset (cursor) paging: the client sends the last key it saw, and the query asks for rows after it, such as WHERE (LastName, FirstName, Id) > (@last...), which an index can seek directly. It stays fast at any depth and is stable when rows are added, but it cannot jump to page 400; keep offset paging for screens that need page numbers.

How to answer:

Explain why OFFSET reads and discards rows, describe keyset paging and its index needs, and choose per use case: infinite scroll and exports versus numbered pages.

Model binding and validation

FWhat is model binding in ASP.NET Core?concept

Turning request data into action parameters: route values, query string values, the JSON body, headers, and services. With [ApiController], a complex type such as CreateStudentRequest is read from the body, and simple types from the route or query string.

FHow do you add validation rules to a request model?coding

With data annotation attributes such as [Required], [StringLength(50)], [Range], and [RegularExpression] on its properties or record parameters. With [ApiController], invalid requests get 400 with ValidationProblemDetails before the action runs.

FWhat does CreatedAtAction return, and why use it?coding

A 201 Created response with the new resource as the body and a Location header built from another action's route, such as the GET for one student. Building the URL from the route keeps it correct when routes change, unlike a hand-written string.

MHow do you validate a rule that compares two fields?coding

Implement IValidatableObject on the request type and return ValidationResult objects from Validate, naming the fields involved, such as an age rule over DateOfBirth and AdmissionDate. ASP.NET Core calls it only after every attribute has passed.

MWhy might a JSON enum value like "Male" fail to bind, and how do you fix it?concept

System.Text.Json reads and writes enums as numbers by default, so a name cannot be converted and the request gets 400. Register JsonStringEnumConverter in the JSON options so enums travel as names, which are clearer and stay stable when enum values are reordered.

MIn what order are binding and validation checks applied, and why does it matter?concept

Content type (415 if not JSON), then binding (400 for values that cannot be converted, stopping at the first), then attributes (every failing field), then IValidatableObject, then the action. Each step runs only if the previous passed, so clients may need two attempts to see all kinds of errors.

SWhere should validation live: the API request model, the domain, or the database?design

In all three, for different purposes. The request model checks shape and format and gives clients clear 400 errors. The domain enforces business rules for every caller, not just the API. Database constraints are the last guard against any client or bug. Duplicated rules are acceptable when each layer has its own reason.

How to answer:

Explain what each layer can see, give SMS examples for each, and how you keep messages consistent, such as mapping domain errors to ProblemDetails.

SA mobile app sends dates like 12/04/2016 and some records are wrong. What do you change?scenario

Accept only an unambiguous format, ISO 8601 (2016-04-12), which DateOnly binds directly, and reject anything else with a 400 that names the field. Fix the app to send ISO dates, and check existing data for swapped day and month values.

How to answer:

Identify the ambiguity, enforce a strict contract, communicate it in the OpenAPI document, and plan the data repair.

AHow would you design input validation and error reporting for a large API used by several client teams?design

One request DTO per operation with declarative rules, cross-field rules in IValidatableObject or a validation library, one error format (ValidationProblemDetails with stable keys and messages), rules visible in the OpenAPI document, and tests for each rule. Keep business rules in the domain and map their errors to the same format.

How to answer:

Cover consistency, documentation, localisation of messages if needed, and how clients can rely on error keys. Mention when a library like FluentValidation is worth it.

Responses and errors

FWhat is ProblemDetails?concept

A standard JSON format for HTTP error responses (RFC 9457), with type, title, status, detail, and instance, plus your own extension fields such as errorNumber. ASP.NET Core can produce it for every error, so clients parse one shape everywhere.

FWhich status code should a successful POST that creates a resource return?concept

201 Created, with a Location header giving the new resource's address and, usually, the resource in the body. In a controller, CreatedAtAction builds both from another action's route.

FWhy should an API not return exception details in a 500 response?concept

Stack traces and messages reveal file paths, library versions, SQL, and internal names that help attackers, and they mean nothing to users. Return a general ProblemDetails with a traceId, and write the full exception to the server log.

MWhen would you return 400, 409, and 422?concept

400 when the request is malformed or fails field validation. 409 when a valid request conflicts with the current state, such as a duplicate admission number or a record changed by someone else. 422 when a well-formed request breaks a business rule, such as paying more than the balance.

MHow do you handle exceptions globally in ASP.NET Core 8 and later?coding

Register AddProblemDetails() and one or more IExceptionHandler classes with AddExceptionHandler<T>(), and put app.UseExceptionHandler() first in the pipeline. Each handler's TryHandleAsync returns true for exceptions it answers; anything unhandled becomes a 500 ProblemDetails.

MWhat does UseStatusCodePages add when ProblemDetails is registered?concept

It fills empty error responses, such as a 404 from a failed route constraint, with a ProblemDetails body. Without it, those responses have no body, so clients see two different error shapes.

SA client retries every failed payment three times. Some failures can never succeed. What do you change in the API?scenario

Return 4xx codes for client-side and rule failures, such as 422 for amount above balance and 409 for conflicts, so the client knows retrying is pointless, and 5xx only for real server failures, which may be retried. Include a machine-readable errorNumber, and make payments idempotent so a retried request cannot charge twice.

How to answer:

Explain status codes as instructions for client behaviour, give the mapping, and mention idempotency keys for safe retries of money operations.

SShould controllers catch domain exceptions with try/catch? What is the alternative?design

Usually not: repeated try/catch blocks drift apart and hide the action's real work. Let domain exceptions carry a meaning, such as an error number, and translate them in one place: an IExceptionHandler that maps them to status codes and ProblemDetails. Use controller helpers only for outcomes the controller decides itself, such as not found.

How to answer:

Contrast the two approaches with an example, and mention testing the mapping once and keeping the domain free of HTTP concepts.

AHow would you design an API's error contract for many client teams over several years?design

One format (ProblemDetails) for every error; a documented catalogue of stable error codes, such as the SMS's 500xx numbers, each with its HTTP status; codes and status listed in the OpenAPI document; human messages that may change and machine codes that never do; a traceId for support; and versioning rules for adding codes without breaking clients.

How to answer:

Separate what clients may depend on (status, code) from what may change (text), show where the catalogue lives, and how it is tested and documented.

FWhat is an API response wrapper (envelope), and why do client teams ask for one?concept

A fixed outer shape for every response body, such as status, message, and data. A client can then read every endpoint with one piece of code: check status, show message, use data. Without it, each endpoint and each kind of error can have its own shape, and every app needs special parsing for each.

FIf every response has a status field, why keep using HTTP status codes?concept

Browsers, proxies, load balancers, monitoring, and retry logic read the HTTP status code, never the JSON body. Answering 200 with "status": "error" hides every failure from them. In the SMS, a missing student is still 404 and a broken rule still 422; the wrapper only changes the body.

FWhy does the SMS DELETE endpoint answer 200 with a message instead of 204 No Content?concept

A 204 response can never have a body, so it cannot carry the wrapper. With an envelope, every answer has a body, so a successful delete answers 200 with ApiResponse.Ok("Student ADM-2026-10001 was removed.") and no data.

MHow can a C# design guarantee that an error response never contains data?coding

Use two classes: a base ApiResponse with status, message, errorCode, errors, and traceId, and ApiResponse<T> that adds Data. The Fail method returns the base class, so an error object has no Data property at all, and the JSON cannot contain one even by mistake.

MWhich error paths in ASP.NET Core must be changed so that every error uses the wrapper?coding

Four: exceptions you expect (an IExceptionHandler for SchoolRuleException), every other exception (a last IExceptionHandler that answers 500), automatic validation errors (ApiBehaviorOptions. InvalidModelStateResponseFactory), and empty error responses such as unknown routes or 405 (UseStatusCodePages with a handler). Also set SuppressMapClientErrors, or a bare NotFound() still becomes a ProblemDetails.

MWhat is a traceId in an error response, and how is it used?concept

The id of the request's trace (Activity.Current.Id in ASP.NET Core). The client gets it in the error response; the server logs the real exception under the same id. Support asks the user for the traceId and finds the exact log entry, without the response ever exposing exception details.

SAfter adding an IExceptionHandler for 500s, production crashes no longer appear in the logs. What happened, and how do you fix it?scenario

Since .NET 10, the exception handler middleware does not write its own log entry when an IExceptionHandler returns true. The handler answered the client but nobody logged the exception. Fix it by logging inside the handler with ILogger, including the same traceId the response carries, and add a test or check that a crash produces a log entry.

How to answer:

Start from the symptom, name the framework behaviour change, give the fix (log in the handler with the traceId), and say how you would stop it happening again.

SYour team is split: some want a response envelope, others want plain bodies with ProblemDetails (RFC 9457) for errors. How do you decide?design

ProblemDetails is a standard: tools, gateways, and generated clients understand it, and success bodies stay plain. An envelope gives one shape for success and error, which many front-end and mobile teams prefer, but it is a house format that every client must learn. Decide by who consumes the API: public or partner APIs lean to standards; one team's own apps often lean to an envelope. Either way, keep real status codes, never leak exception details, and document it in OpenAPI. The SMS chose an envelope for its own apps and keeps the same status codes.

How to answer:

Compare both on standards, tooling, and client simplicity, tie the choice to the API's consumers, and name what must stay true in both designs.

AMany services each have their own response format, and clients struggle. How would you standardise the response contract across them?design

Agree one contract (envelope or ProblemDetails) and publish it as a small shared library with the common Ok and Fail methods, the error handlers, and the validation setup, so services adopt it with a few lines. Define error codes per domain, require a traceId linked to distributed tracing, describe the shapes in each OpenAPI document, and add contract tests that call every endpoint's error paths. Roll it out with API versioning, so existing clients keep the old shape until they move.

How to answer:

Cover the contract, how teams adopt it cheaply, how it is enforced (tests, OpenAPI), observability, and a migration plan that does not break existing clients.

Building endpoints

FWhat does CRUD mean, and which HTTP methods does each part use?concept

Create, read, update, and delete: the basic operations on a record. In a REST API, create is POST, read is GET, update is PUT or PATCH, and delete is DELETE, each on a resource address such as /api/students/{admissionNumber}.

FWhat is a soft delete, and why use it for students?concept

Marking a record as deleted, such as IsDeleted = 1 with a timestamp, instead of removing the row. Students have invoices, payments, attendance, and marks; removing the row would break that history or be refused by foreign keys. A query filter hides soft-deleted rows everywhere.

FWhat status codes should PUT and DELETE return?concept

PUT: 200 with the updated resource, or 204 with no body; 400 for invalid input; 404 when the resource does not exist. DELETE: 204 when removed; 404 when there is nothing to remove, including a second delete of the same resource.

MHow would you implement optional search and filter parameters on a list endpoint with EF Core?coding

Start from the IQueryable, add a Where only for each parameter that was given, then count, sort, and page. EF Core turns it into one parameterised SQL query; StartsWith becomes LIKE 'text%', which can use an index, and a related-table filter becomes EXISTS.

MWhat is an EF Core SaveChanges interceptor, and what would you use one for?coding

A class EF Core calls during SaveChanges, such as before the SQL is sent. It can inspect and change tracked entities, for example setting UpdatedAt on every modified entity with audit columns, so no repository or service has to remember to do it.

MWhy is a uniqueness check in code not enough, even when it runs before every insert?concept

The check cannot see rows hidden by filters, such as soft-deleted records, and two requests can both pass it at the same moment before either inserts. Only the database sees every row at insert time, so keep a UNIQUE constraint and translate its error (2627 or 2601) into a clear 409.

SA school asks to permanently delete a student's data on request. How does that fit with soft delete?scenario

Soft delete covers normal removals. A permanent erasure request is a separate, audited process: check legal retention rules for fee and academic records, anonymise or remove personal fields while keeping financial and statistical records that must stay, record who approved it, and run it in a transaction.

How to answer:

Distinguish routine removal from data-protection erasure, mention retention obligations, anonymisation, auditing, and backups that also hold the data.

SWhen would you add PATCH to an API that already has PUT?design

When clients often change one or two fields of a large resource, when several clients edit different fields of the same record, or when sending every field risks overwriting newer data. Use a clear format, such as JSON Merge Patch or JSON Patch, validate the result after applying it, and combine it with a concurrency check.

How to answer:

Weigh payload size and lost-update risk against complexity, name the patch formats, and explain validating the patched result rather than the patch itself.

AHow would you design search for a system with millions of students across many schools?design

Prefix searches on indexed name and number columns for the common cases, always scoped by school and paged with a stable sort. For "contains", spelling mistakes, or several fields at once, use full-text search or a dedicated search engine fed by changes from the database, and keep the API contract the same whichever engine answers.

How to answer:

Start from the query patterns, show the index each needs, explain where LIKE stops scaling, and how a search engine is kept in sync.

FWhat is master data, and how is it different from transaction data?concept

Master data is reference information that other features depend on, read constantly and changed rarely, such as academic years, classes, sections, and subjects. Transaction data records daily activity, changes constantly, and grows large, such as attendance, marks, and payments.

FWhat does the Cache-Control header with max-age do?concept

It tells clients and caches how long they may reuse a response without asking the server again, such as Cache-Control: public,max-age=3600 for one hour. In ASP.NET Core, [ResponseCache(Duration = 3600)] adds it; the server itself stores nothing.

FWhy return classes together with their sections in one response?design

Screens almost always need both. Returning them nested saves the client from making one more request per class, the N+1 problem on the client's side, and EF Core can build the whole tree with one SQL query.

MWhy should error responses never be cached, and what does ASP.NET Core do about it?concept

A cached error, such as a 404 for the current academic year, could be shown long after the problem was fixed. ASP.NET Core's exception handling adds Cache-Control: no-cache,no-store to the error responses it writes, so clients and proxies do not keep them.

MHow do you choose a max-age for an endpoint?design

By how long the data may be stale without harm. Subjects and academic years can be an hour or more; classes with live student counts a few minutes during admissions; attendance or balances not at all. When in doubt, start short and lengthen it after watching how the data changes.

MWhat does the EF Core warning about a global query filter on the required end of a relationship mean?concept

A principal entity, such as Student, is filtered, but a dependent that requires it, such as Enrollment, is not. Queries on the dependent then still return rows whose principal is hidden, such as enrollments of removed students being counted. Give the dependent a matching filter through its navigation, or make the relationship optional.

STwo subjects, Maths and Mathematics, exist and marks are split between them. How do you fix it and prevent it?scenario

Merge them: choose one, move every mark, exam paper, and allocation to it in one transaction, then remove the other. Prevent it with unique constraints on both the code and the name, translated into a 409, and restrict master data changes to administrators.

How to answer:

Cover the data repair, its transaction and verification, the constraints, the API's answer, and the permission rule.

SAn app loads the same subject list thousands of times each morning. What can the API do?scenario

Send Cache-Control with a suitable max-age so clients reuse the answer, and support conditional requests with ETag so clients that do ask get 304 Not Modified. Add server-side caching for the data itself if the database is still busy, and invalidate it when an administrator changes the list.

How to answer:

Separate client caching, conditional requests, and server caching, and explain invalidation when master data changes.

AHow would you manage master data shared by many schools in a multi-school product?design

Separate global reference data, such as a standard subject catalogue, from school-specific data, such as sections and timetables; scope every school-specific row by school; version changes to shared data; cache per school with clear invalidation; and let administrators change only their own school's data.

How to answer:

Discuss global versus tenant-owned data, isolation, caching keys, change control, and migration of existing schools when the shared catalogue changes. Module 13 builds this.

FWhat is a many-to-many relationship? Give an example from a school system.concept

Each row on one side can relate to many rows on the other, and the other way round. A student has several guardians, and a guardian can have several children at the school. It is stored in a junction table, such as StudentGuardians, with one row per link.

FWhat is a composite key?concept

A primary key made of more than one column. In StudentGuardians it is (StudentId, GuardianId): the pair is unique, so a guardian is linked to the same student only once. In EF Core it is declared with HasKey(sg => new { sg.StudentId, sg.GuardianId }).

FShould an endpoint return 404 or an empty list when a student has no guardians?design

An empty list with 200: the student exists, and the answer to "which guardians?" is "none". Return 404 only when the parent resource, the student, does not exist, so clients can tell the two cases apart.

MHow do you map a junction table that has its own columns in EF Core?coding

As an entity of its own, such as StudentGuardian, with a composite key and two one-to-many relationships, one to each side. EF Core's automatic many-to-many mapping hides the junction table and has no place for columns like Relationship or IsPrimaryContact.

MHow do you enforce 'at most one primary contact per student' in SQL Server?coding

With a filtered unique index on StudentId that includes only rows where IsPrimaryContact = 1. Students may have many guardians, but only one row per student can be in the index, so a second primary contact fails with error 2601.

MHow can one controller serve routes under two different resources?coding

Leave the controller without a class-level route, or ignore it, and start each action's route with "/", which makes it absolute, such as [HttpGet("/api/students/{admissionNumber}/guardians")] and [HttpGet("/api/guardians/{guardianId:int}/students")].

SSome students show two primary contacts after two years of use. How do you fix the system?scenario

Fix the code so setting a new primary clears the old one in the same save; clean existing data with a script that keeps one primary per student by an agreed rule and reviews unclear cases; then add a filtered unique index so the data can never break the rule again, whatever code or tool writes to it.

How to answer:

Cover the code fix, the data repair with its decision rule and verification, and the database constraint as the lasting guard.

SWhich personal details should a list endpoint return, and how do you decide?design

Only what the screens using it need: a staff list needs names, codes, and work emails, not personal phones or dates of birth. Put more sensitive details on single-resource endpoints that fewer roles may call, and check every DTO against who will see it.

How to answer:

Mention data minimisation, roles and authorization (Module 12), logging and caching risks of personal data, and reviewing DTOs as part of design.

AHow would you model people who play several roles, such as a teacher who is also a parent at the school?design

Separate the person from their roles: one person record with shared identity and contact details, and role records such as Teacher and Guardian that reference it, each with role-specific data and permissions. Logins attach to the person, and authorization checks roles, so one sign-in can see both the staff and the parent views.

How to answer:

Compare per-role tables (as in the SMS today) with a person-plus-roles model, discuss duplication, identity, and authorization, and how you would migrate.

FWhat is a race condition in a web API? Give an example.concept

Two requests run at the same time and the result depends on their timing. Two clerks enrolling into one section both read "highest roll number 0" and both save roll number 1; one of them acted on information that was no longer true.

FWhy put the enrollment rules in a service rather than in the controller?design

The rules (active student, not already enrolled, capacity, next roll number) are business logic that must hold for every caller, and they need several repository calls in one transaction. A service keeps them in one tested place; the controller only translates HTTP to the service call.

FWhich status codes fit 'section is full', 'student not found', and 'student not active'?concept

Section full: 409 Conflict, because the request clashes with the section's current state. Student not found: 404. Student not active: 422, a business rule. Each also carries an SMS error number, such as 50034, 50030, and 50032.

MHow do you make 'check, then insert' safe when two requests act on the same group of rows?coding

Serialize them on something that represents the group: start a transaction and lock its row, such as SELECT ... FROM Sections WITH (UPDLOCK, ROWLOCK), then check and insert, then commit. Requests for the same section wait; other sections are unaffected. Keep a unique constraint as a final guard.

MHow do you generate the next number within a group, such as a roll number per section?coding

While the group is locked, read the highest existing number and add one, then insert, all in one transaction; back it with a unique constraint on (group, number). Without the lock, two requests compute the same number. A sequence or identity does not fit, because numbering restarts per group.

MWhy might a soft-delete query filter cause duplicate numbers, and how do you avoid it?concept

Rows hidden by the filter still exist and still hold their numbers in the unique constraint. If the highest number is read through the filter, a removed student's number can be handed out again and the insert fails. Read the highest number with IgnoreQueryFilters, while counting capacity with the filter.

SHow do you test that a lock really serializes concurrent requests?design

Make the timing deterministic: in the test, take the lock with one connection and hold it, start the operation on a second connection, check that it has not finished after a delay, release the lock, and check the operation then completes correctly. Sending two requests by hand rarely overlaps reliably.

How to answer:

Explain why naive parallel tests are flaky, describe the hold-the-lock pattern, and mention cleaning up test data when the code under test runs its own transaction.

SWhen would you choose pessimistic locking over optimistic concurrency?design

When conflicts are likely and the work under the lock is short, such as many clerks enrolling into the same sections in admission week: waiting briefly is better than failing and retrying. Choose optimistic concurrency when conflicts are rare or when a user thinks between reading and saving, where holding a lock would block others for minutes.

How to answer:

Compare conflict rate, lock duration, user experience, and deadlock risk, with an example of each from the SMS (enrollment versus fee payment screens).

AAdmission week overloads one popular section with waiting enrollments. How would you scale the design?design

Keep the lock scope minimal and the locked work short; move slow work (notifications, documents) after the commit or to a background queue; consider assigning roll numbers in a later batch step so admission only reserves a place; and use a queue per section if demand truly exceeds what one lock can serve.

How to answer:

Identify the contention point, reduce work inside it, separate reservation from numbering, and discuss asynchronous processing and its effect on the user experience.

FWhy take a whole section's attendance in one request instead of one request per student?design

One request is faster on weak networks, and it can be saved in one transaction, so a register is never half saved. It also lets the API check the register as a whole, such as that every enrolled student is included exactly once.

FWhat does it mean that the attendance PUT is idempotent, and why does it matter?concept

Sending the same register twice leaves the same result as sending it once: the second request created and updated nothing. On school Wi-Fi, apps retry timed-out requests, so retries must be harmless instead of creating duplicate records.

FHow is the attendance percentage calculated in the SMS?concept

(Present + Late) divided by all recorded days, times 100, the same rule as Lesson 18's SQL. Kabir had 2 present days out of 3 recorded in July, which is 66.67%.

MWhat is TimeProvider, and why use it instead of DateTime.Now?coding

.NET's built-in abstraction for the current time. The app registers TimeProvider.System; tests pass a provider stopped at a fixed moment. Rules such as "no future dates" then give the same result on every test run, instead of changing as real days pass.

MHow do you implement 'update if it exists, otherwise insert' for a batch with EF Core?coding

Load the existing rows for the batch's key (section and date) as tracked entities, then for each item update the matching entity if its values differ, or add a new one. One SaveChangesAsync writes the updates and inserts in one transaction, with a unique constraint as the final guard against duplicates.

MWhy should attendance reports be aggregated in SQL rather than in C#?concept

A day has about 10,000 records. GROUP BY in SQL Server sends back ten rows of counts; loading the records into the API to count them would move all 10,000 rows for the same ten numbers. With an index on the date, the SMS's daily summary took about 120 ms over 600,000 rows.

SReports show some students above 100% attendance. What likely happened, and how do you prevent it?scenario

Duplicate records for the same student and day, usually from retried requests that inserted again. Repair the duplicates, add a unique constraint on (enrollment, date) if missing, and make the save idempotent: one request per register, updating existing records instead of inserting blindly.

How to answer:

Work from symptom to cause (duplicates), name the missing guards (constraint, idempotency), and cover the data repair with verification.

SShould the attendance endpoint accept a partial register? Discuss the trade-off.design

Requiring the complete register prevents a forgotten student from looking present or unrecorded, and keeps every day's data comparable. Accepting partial registers suits workflows such as marking late arrivals later; then the API needs explicit "not marked" handling in reports. The SMS requires complete registers and allows corrections by sending the register again.

How to answer:

Weigh data quality against flexibility, describe what reports must do in each design, and say how corrections work.

AAttendance grows by 10,000 rows a day across many schools. How would you keep it fast and manageable for years?design

Index for the main access paths (date; enrollment and date), keep reports aggregated in SQL, pre-compute daily and monthly summaries in a summary table updated after each register, archive or partition old years by date, and serve dashboards from the summaries instead of the raw rows.

How to answer:

Estimate the growth, then cover indexing, summary tables, partitioning (SQL Extras), archiving, and how reports stay correct when summaries are updated.

FHow would you design REST routes for entering exam marks in a school system?design

Give each paper (one subject's exam for one class) its own address and treat a section's marks sheet as one resource: GET and PUT /api/exam-papers/{paperId}/marks?sectionId=19. Exams and their papers are read with GET /api/exams and /api/exams/{examId}/papers?classId=10, and a report card with GET /api/students/{admissionNumber}/report-cards/{examId}.

FWhy should an absent student not simply get 0 marks?concept

Absent and a score of 0 mean different things, and reports must show the difference. The SMS stores IsAbsent = true with null marks; the report card shows "Absent", and a line must have marks or absent, never both (error 50053).

FWhen does the SMS report card show a percentage and a final result?concept

Only when every paper of the exam has been entered. Until then the result is "Incomplete" and the percentage is null, because a missing paper would make the percentage look higher. A complete card is "Pass" only if every paper reached its pass marks.

MWhen should a validation check move into a shared class?design

When a second feature needs exactly the same rule. In the SMS, the register check (every student once, nobody else) was written for attendance; marks sheets needed it too, so it moved to RegisterCheck, and both services call it. Sharing earlier, with only one user, often guesses the wrong shape.

MWhy keep report card rules in a service instead of in the SQL query?design

The query only returns what is stored: each paper and the student's mark if there is one. The service decides Pass, Fail, Absent, grades, and the result. Rules change between schools and boards, and in C# they can be unit tested without a database, as the SMS report card tests do.

MA database trigger raises THROW 50001 for invalid marks. How should the API handle it?coding

Catch the SqlException with Number 50001 in the repository and throw the same SchoolRuleException 50001 that the service raises for the same rule. The exception handler then answers 422 with errorCode 50001, whether the C# check or the database caught the problem. Without this, the client gets a 500.

SPrinted report cards showed percentages that were too high for some students. How do you investigate and prevent it?scenario

Compare a wrong card with its marks: the usual cause is a percentage calculated from the papers entered so far while some papers were still missing. Fix the rule (no percentage, result "Incomplete", until every paper is entered), add tests for incomplete cards, and give the office a list of missing papers before printing. Reprint the affected cards.

How to answer:

Reproduce with real data, name the rule that was wrong, fix it with a test, and add a process check so incomplete data cannot reach print again.

SShould the 'marks within the maximum' rule live in the API, the database, or both?design

Both. The API checks first and gives a clear message for the user. The database trigger is the final guard for every program that writes marks, such as imports or scripts. Both use error number 50001, and the repository translates the database's error, so clients see one consistent error.

How to answer:

Explain defence in depth, who writes to the database besides the API, and how to keep both checks consistent for clients.

AEach school in a group uses different grade scales and pass rules. How would you design the report card logic?design

Keep the data query the same and make the rules configurable per school: grade scales as data or as strategies (the IGradeScale idea from Lesson 56), pass marks per paper, and result rules per board. Version the rules so old report cards can be reproduced exactly, cache them per school, and cover each school's rules with tests built from real past results.

How to answer:

Separate stored data from rules, show how rules vary per school, and cover versioning, reproducibility, and testing.

FWhat is set-based SQL, and why use it to create invoices for a whole class?concept

Set-based SQL handles many rows in one statement, such as INSERT ... SELECT, instead of one statement per row in a loop. The SMS bills a class of 1,000 students with two inserts and a summary in one transaction and one round trip, in about 0.3 seconds, instead of about 4,000 separate statements.

FWhat is a SQL Server sequence, and why use one for invoice numbers?concept

A sequence is a database object that hands out the next number each time NEXT VALUE FOR is called, safely across many users at once. MAX(number) + 1 can give two requests the same number; a sequence never does. The SMS formats the value as INV-2026-NNNNNN.

FWhy are there gaps in invoice numbers, and is that a problem?concept

A sequence never takes a number back, even when the transaction that used it rolls back or the row is later deleted. In the SMS, test runs used 2,000 numbers, so the next real invoice was INV-2026-002013. Gaps are normal; what must never happen is a duplicate, which the unique constraint on InvoiceNumber prevents.

MHow do you make a batch endpoint, such as 'bill this class', safe to run twice?design

Let the batch skip what is already done and report it. The SMS insert uses NOT EXISTS to skip students who already have an active invoice; the second run reported 0 created and 1,006 already invoiced. A unique index (one active invoice per enrollment) is the final guard.

MHow do you add an item row for each new invoice created by an INSERT ... SELECT?coding

Capture the new rows with OUTPUT INSERTED.Id INTO a table variable, then insert the items with INSERT ... SELECT from that table variable joined to the fee lines (a CROSS JOIN, since every invoice gets every line). This works because FeeInvoices has no trigger; on a table with triggers, OUTPUT must use INTO, as here.

MHow do you build a paged report that also shows totals for everything, in one round trip?coding

Send two statements with the same FROM and WHERE: one returns COUNT and SUM over the whole filter, the other returns one page with ORDER BY and OFFSET/FETCH. Read both with Dapper's QueryMultiple. Totals computed from the page alone would change with the page size.

SA server restart halfway through billing left half a class with invoices; a retry then billed the first half twice. How do you fix it?scenario

Repair first: find enrollments with two active invoices and cancel the duplicates, with an audit note. Then fix the design: bill the class in one set-based transaction, so a failure leaves nothing half-done; skip students already billed, so a retry is harmless; and add a filtered unique index so the database refuses a second active invoice.

How to answer:

Separate the data repair from the design fix, and explain how atomicity, idempotency, and a database constraint each prevent a part of the problem.

STwo clerks click 'Generate invoices' for the same class at the same moment. What happens in your design?scenario

Each run first takes an application lock for the class (sp_getapplock), so the second clerk waits until the first commits, then its NOT EXISTS check finds every student already billed and creates nothing. If anything slipped past, the filtered unique index would refuse the duplicate and the API would answer 409 (50017). The first SMS version used UPDLOCK, HOLDLOCK on the check instead, and under load two runs could deadlock (error 1205); the application lock removed that.

How to answer:

Walk through both transactions step by step, name the lock and the constraint, and say what the second clerk sees.

ABilling must run for 1,000 schools at the start of each term. How would you design it?design

Run billing as background jobs per school and class, not inside web requests. Each job is set-based, one transaction, and idempotent, so failed jobs can simply be retried. Record each run (who, when, counts) for audit, spread the load over time, give each school its own number series, and notify the office when its run is done. Monitor failures and totals per school.

How to answer:

Cover moving work off the request path, idempotent retries, data partitioning per school, numbering, auditing, and monitoring.

FWhy build a dashboard endpoint instead of letting the screen call the existing endpoints?design

One request is quicker than six, especially on phones and slow networks, and it is less work for the server. The endpoint is shaped by the screen, not by the tables. The SMS keeps the detailed endpoints for their own screens and adds GET /api/dashboard/summary for the first screen.

FWhat does Dapper's QueryMultiple do, and what must you be careful about?coding

It sends several SQL statements in one round trip and returns a reader for their results. You read each result in the same order as the statements; reading out of order maps the wrong rows. The SMS dashboard reads five results this way.

FWhat is a covering index?concept

An index that holds every column a query needs, so SQL Server reads only the index and never the table. INCLUDE adds columns to the index without making them part of the key. In the SMS, adding EnrollmentId to the attendance date index cut one day's count from 1,606 pages read to 44.

MHow do you find which statement in a SQL batch is slow?coding

Run it with SET STATISTICS TIME ON to get the CPU and elapsed time of each statement, and SET STATISTICS IO ON to see how many pages each one reads from each table. The SMS dashboard's attendance statement took 126 ms and read 1,606 pages, which pointed straight at a missing index column.

MAn index includes Status, yet a query on that table still reads the table. Why?scenario

The query needs another column the index does not hold. In the SMS, the dashboard joins each attendance record to its enrollment, so it needs EnrollmentId as well as Status. Rebuilding the index with INCLUDE (EnrollmentId, Status) and DROP_EXISTING = ON made it cover the query.

MWhat does [ResponseCache(Duration = 60)] do on an ASP.NET Core action?concept

It adds the header Cache-Control: public,max-age=60, so browsers and proxies may reuse the response for a minute. It does not cache anything on the server by itself. It suits numbers such as a dashboard that may safely be a little old.

SYou added an index to speed up a query, but the timings did not change at all. What do you check?scenario

Check that the change was really made: in the SMS, the script's re-run check looked for a column that was already included, so it skipped the rebuild. Then read the index definition from sys.index_columns, and compare pages read with STATISTICS IO before and after. Only trust a fix once the numbers move.

How to answer:

Start from verifying the change itself, then measure with the right tools, and explain how you would prevent a silent no-op in future scripts.

SA dashboard test fails sometimes when the whole suite runs, but never alone. What is likely wrong?scenario

It depends on data that other tests change at the same time: test classes run in parallel. In the SMS, fee totals change while FeeBillingTests creates invoices, so the dashboard test checks only numbers that no other test changes. Alternatives are separate test data, a collection that runs tests one after another, or a database per test run.

How to answer:

Explain parallel test execution, shared data, and the options to isolate tests, with their costs.

AThe dashboard is opened by thousands of users across many schools. How would you keep it fast?design

Keep one round trip per dashboard and covering indexes for its queries. Cache the result per school for a short time on the server (memory or a distributed cache), refreshed in the background. Pre-compute heavy numbers such as daily attendance into summary tables. Send Cache-Control so clients reuse answers, and watch query times per school in monitoring.

How to answer:

Layer the answer: query efficiency, server-side caching with clear freshness rules, pre-computation, and measurement in production.

Data access in the API

FHow can you switch an API's data access from EF Core to Dapper without changing the controllers?concept

Keep data access behind interfaces such as IStudentRepository, and let one registration method decide the classes. Controllers and services only ask for the interfaces. In the SMS, AddSchoolData changed from the EfCore classes to the Dapper classes, and no controller changed.

FEF Core had a soft-delete query filter. What must you do instead when you use Dapper?concept

Write the condition yourself, such as IsDeleted = 0, in every query that reads the table, including joins and counts. Dapper runs exactly the SQL you write. A test with one removed student catches a forgotten condition.

FWhy might a team choose Dapper for an API instead of EF Core?concept

Dapper runs SQL that the team writes and can read, so queries are exact and easy to review with a database team, and its overhead is very small. EF Core gives change tracking, LINQ, migrations, and less code for simple writes. Many teams use EF Core for writes and Dapper for heavy reads; the SMS chose Dapper for the whole API.

MDapper threw SqlException 547 when saving an attendance status. What went wrong?scenario

The enum AttendanceStatus was passed as it is, and Dapper sends an enum's number (Late is 2). The Status column holds names, and CK_AttendanceRecords_Status refused the number. Pass Status.ToString(). EF Core did this conversion for us with HasConversion<string>().

MHow do you save several related changes together with Dapper?coding

Open the connection, start a SqlTransaction, pass the transaction to every Dapper call, and commit at the end. If anything throws, disposing the transaction without committing rolls everything back. In the SMS, the invoice update and the new payment, and the attendance updates and inserts, each use one transaction.

MHow do you implement optimistic concurrency with a RowVersion column in Dapper?coding

Read the RowVersion with the row. Update with WHERE Id = @Id AND RowVersion = @RowVersion, and read the new value with OUTPUT INSERTED.RowVersion. If no row comes back, someone else saved first: roll back and report a conflict. The SMS reports it as error 50014, just as the EF Core version did.

SAfter switching to Dapper, a payment is saved but the API answers 500 with a NullReferenceException. How do you find and fix it?scenario

Use the traceId to find the log entry and its stack trace. In the SMS it pointed to the mapping code, which read payment.FeeInvoice. EF Core's relationship fix-up had always filled that link; Dapper does not. The right fix is in the entity: RecordPayment sets FeeInvoice = this, so the object model is correct whichever tool loads it. Then add a test that checks the link.

How to answer:

Go from symptom to log to cause, explain the hidden ORM behaviour, and say why the fix belongs in the domain model rather than the repository, with a test to keep it fixed.

SHow would you prove that replacing a data-access layer did not change the API's behaviour?design

Run the same tests against both implementations (the SMS enrollment tests run once per tool, including the lock test). Call the same requests on the old and new builds and diff the responses; the SMS compared 20 GET answers and found no difference. Exercise every write path, then compare timings. Keep the old implementation until all of this passes.

How to answer:

Cover automated tests, side-by-side comparison of real responses, write paths, performance, and a safe rollout with the old code still available.

AYour company's APIs use EF Core, and one team wants Dapper. How would you set a data-access standard across many teams?design

Agree what must be true whatever the tool: repositories behind interfaces, one transaction per business change, concurrency checks, soft-delete rules, parameterised SQL, and integration tests against a real database. Allow EF Core for most CRUD and Dapper where SQL control or speed matters, decided per service and recorded. Provide shared helpers (connection factory, transaction helper, row-to-DTO patterns) and review checklists for the things EF Core did implicitly. Measure before switching for speed.

How to answer:

Separate the non-negotiable rules from the tool choice, give criteria for choosing, and explain how you support teams and enforce the rules.

Filters

FWhat is a filter in ASP.NET Core MVC?concept

Code that runs before and after controller actions, inside MVC, for jobs many actions share. Filters can read the action, its attributes, and its bound arguments, and can answer the request themselves. The SMS uses a filter for read-only mode and another for checking date ranges.

FIn what order do the filter types run?concept

Authorization filters, then resource filters, then model binding and validation, then action filters around the action, then exception filters if it throws, then result filters around writing the result. Resource filters also run again at the very end, on the way back.

FWhen would you use middleware instead of a filter?concept

When the job applies to every request, whatever happens next, including requests for files or unknown routes, which never reach a filter. Use a filter when the job depends on which action runs or on its bound arguments. In the SMS, request timing is middleware; read-only mode and date checks are filters.

MHow does a filter stop a request from reaching the action?coding

It sets context.Result (or, in an async filter, does not call next). That result becomes the response, and the action and the later filters never run. The SMS's ReadOnlyModeFilter sets a 503 ApiResponse this way.

MWhy might a filter answer 503 to a request whose body is invalid, instead of 400?scenario

It is a resource filter, which runs before model binding and validation. In the SMS read-only mode, an empty admission request got 503 because the filter refused it before validation ever read the body. An action filter would run after validation, and the client would get 400 first.

MAn attribute filter needs a service from dependency injection. How do you supply it?coding

Attribute filters are created by .NET, not by dependency injection, so their constructors cannot ask for services. Use [ServiceFilter(typeof(MyFilter))] (the filter is registered and taken from DI) or [TypeFilter(typeof(MyFilter))] (created with services filled in), or register the filter globally with options.Filters.Add of the type, as the SMS does for ReadOnlyModeFilter.

SThe school must stop all changes for two days at year end, but parents must still read report cards. How do you build it?design

A global resource filter that refuses every non-GET request with 503 and Retry-After while a setting is on, with an attribute to exempt actions such as sign-in. Read the setting with IOptionsMonitor, so the office can switch it without a restart or deployment. Keep the message clear, and test both modes.

How to answer:

Choose the mechanism (a resource filter), explain why it runs before binding, how the switch changes at run time, which actions are exempt, and what status and headers clients get.

SYou changed a setting in appsettings.json while the API was running, but a filter still uses the old value. Why?scenario

The filter reads the setting through IOptions, which binds the value once. IOptionsMonitor.CurrentValue (or IOptionsSnapshot per request) sees reloaded configuration. Also check the file is the one the app actually reads (the content root) and that reloading is on, which is the default for appsettings.json.

How to answer:

Name the three options interfaces and when each sees changes, and how you would verify the reload.

APayments are sometimes recorded twice when a phone app retries after a timeout. How would you design idempotency for the API?design

Clients send an Idempotency-Key header with each payment. A resource filter (or middleware) stores the key with the response in a shared store (database or distributed cache) for a limited time, and when the same key arrives again it replays the stored response instead of running the action. Store keys per user, handle two identical requests arriving at once with a unique constraint or lock, and reject a reused key with a different body.

How to answer:

Cover the client contract, where keys and responses are stored, concurrent duplicates, expiry, and how it works across several servers.

Logging

FWhat are log levels, and when would you use Warning instead of Error?concept

Levels rank how important an entry is: Trace, Debug, Information, Warning, Error, Critical. Warning is for something unusual that was handled, such as a school rule refusing a request or a slow request; Error is for a failure the user saw, such as an unhandled exception. Settings choose the minimum level per category.

FWhat is structured logging?concept

Writing log entries from a message template with named holes, such as "Payment {ReceiptNumber} recorded", so each value is kept as a separate property. Log systems can then search and count by property (ReceiptNumber = RCP-2026-000069) instead of parsing text.

FWhat should never be written to logs?concept

Passwords, access tokens, signing keys, and personal data such as names, birthdays, phone numbers, and addresses, and whole request or response bodies that may contain them. Logs are read by more people and kept in more places than the database. The SMS logs ids only.

MWhy is logger.LogInformation($"Payment {receipt}") a bad habit?coding

String interpolation turns the value into plain text, so it cannot be searched as a property, and the string is built even when the level is turned off. Use a message template, LogInformation("Payment {ReceiptNumber}", receipt), or a [LoggerMessage] method.

MWhat does the [LoggerMessage] attribute give you?coding

The compiler generates the body of a static partial logging method: fast (no boxing, the template parsed once), typed (a missing argument is a build error), with a fixed event id and level. The SMS keeps every deliberate message in one SchoolLog class this way.

MHow do you add the current user to every log entry of a request?coding

Open a logging scope after authentication, for example in a small middleware: logger.BeginScope("UserId:{UserId}", userId). Every entry written inside the scope carries UserId, and the JSON formatter stores it as a property. Code that runs outside the scope, such as the exception handler, must add the user to its own message.

SA parent sends a screenshot of an error with a trace id. How do you find what happened?scenario

Search the logs for the trace id. In ASP.NET Core it is the middle part of the response's traceId (00-<TraceId>-<SpanId>-00), and every log entry of that request carries it as a scope. The matching entries show the request path, the user, the rule or exception, and the timing.

How to answer:

Explain how the trace id links the response to the logs, what the entries contain, and why logs must include scopes and be searchable for this to work.

SThe team finds parents' phone numbers and passwords in the API logs. What do you do?scenario

Stop the leak first: remove request-body logging and any log line that includes personal data, and deploy. Then treat the stored logs as an incident: restrict access, delete or redact them according to policy, and ask affected users to change passwords. Prevent it returning with deliberate log messages that take ids only, and tests that check sensitive text never appears in logs.

How to answer:

Order the response: contain, clean up existing data, notify, then prevent with design and tests.

AHow would you design observability for an API that runs on many servers for many schools?design

Structured JSON logs with fixed event ids, sent to a central store; distributed traces with the same trace ids across services (OpenTelemetry); and metrics such as request rates, error rates, and response times per endpoint and school. Add the school and user ids as scopes, alert on symptoms (error rate, slow requests), keep personal data out, and set retention and access rules for each kind of data.

How to answer:

Cover the three signals (logs, traces, metrics), how they are linked, what you alert on, and the privacy and cost limits.

Caching

FWhat is a cache, and what data is worth caching?concept

A cache keeps a copy of an answer close by so the next request does not ask the source again. Cache data that is read far more often than it changes and that may safely be a little old, such as the current academic year or the list of subjects. Never cache data that must be exact, such as a fee balance.

FWhat is the difference between [ResponseCache] and a server-side cache?concept

[ResponseCache] sends a Cache-Control header so each browser or proxy may keep its own copy; the server cannot take it back early. A server-side cache such as HybridCache keeps the copy in the API's memory, serves every client from it, and can remove entries when data changes. They work well together.

FWhat makes a good cache key?concept

It names everything the answer depends on, and nothing else, so two different questions never share an answer. The SMS dashboard key is dashboard:{yearId}:{date}; leaving out the date would return one day's numbers for another. Per-user answers need the user in the key, or should not be cached.

MHow does HybridCache.GetOrCreateAsync behave, and what is stampede protection?concept

It returns the cached value for a key, or runs the given function once, stores its result, and returns it. Stampede protection means that when many requests ask for the same missing key at once, the function runs only once and all of them share the result, instead of all hitting the database together.

MHow do you add caching without changing the code that uses a repository?design

Write a decorator: a class that implements the same interface, holds the real repository, answers reads from the cache, and passes everything else through. Register it in place of the real one. In the SMS, CachedMasterDataRepository wraps the Dapper repository, and no controller or service changed.

MHow do you remove cached answers when the data changes?coding

Give related entries a tag when you store them, and remove the tag in the same code path that changes the data. In the SMS, every subject entry carries the tag "subjects", and AddSubjectAsync calls RemoveByTagAsync("subjects"), so the new subject appears at once instead of after an hour.

SParents see an old fee balance in the app after paying. What went wrong, and what do you change?scenario

The balance was cached, so the app showed the copy from before the payment. Money and other exact, per-person data should not be cached; if a cache is truly needed, remove the student's entry in the payment code path and keep expiry very short. Review every cached item with the question: what harm does the old version cause?

How to answer:

Identify the stale cache as the cause, decide whether the data should be cached at all, and describe invalidation and review rules.

SThe API runs on three servers with in-memory caches. A subject is added on one of them. What happens?scenario

Only that server removes its cached subjects; the other two keep their copies until they expire, so users may see different lists for up to an hour. Options: shorter expiry, a shared distributed cache (such as Redis as HybridCache's second level), or broadcasting removals to every server.

How to answer:

Explain per-server memory caches, the visible effect, and compare the fixes by freshness, cost, and complexity.

AHow would you design caching for an API that serves 1,000 schools?design

Put the school in every key (and tag by school), so no school ever sees another's data. Use a two-level cache: memory on each server for speed, plus a shared distributed cache for consistency and warm restarts. Cache master data and computed dashboards with expiry matched to how often each changes, remove tags in the code that changes data, protect against stampedes, and watch hit rates and memory per school.

How to answer:

Cover isolation between tenants, the cache levels, what is cached and for how long, invalidation, and monitoring.

Versioning

FWhat is a breaking change in an API? Give examples.concept

A change that makes existing clients fail or misread data: removing or renaming a response field, changing a field's type or meaning, making an optional request field required, or changing a status code. Adding a field, an endpoint, or an optional request field is not breaking, because old clients ignore it.

FWhat are the common ways to put a version in an API request?concept

In the URL path (/api/v2/students/...), in a header (X-Api-Version: 2), or in the query string (?api-version=2.0). URL versions are visible and easy to test and cache; headers keep URLs clean but are invisible in links and logs. The SMS uses the URL segment, and the key is to use one style everywhere.

FHow do existing apps keep working when you add versioning to an API that had none?concept

Make unversioned requests mean version 1. In Asp.Versioning, set DefaultApiVersion to 1.0 and AssumeDefaultVersionWhenUnspecified to true. In the SMS, /api/students/... still answers exactly as before, and /api/v1/... is the same endpoint with the version named.

MHow do you serve two versions of the same endpoint in ASP.NET Core controllers?coding

Add Asp.Versioning.Mvc, mark the existing controller [ApiVersion("1.0")], and add a new controller marked [ApiVersion("2.0")] with a route such as api/v{version:apiVersion}/students. Give both the same [ControllerName] so the package groups them and reports both versions.

MWhy might api-supported-versions show only one version on each of two versioned controllers?scenario

Asp.Versioning groups versions by controller name. A StudentsController (1.0) and a StudentsV2Controller (2.0) look like two different APIs, so each reports only its own version. [ControllerName("Students")] on the version 2 class makes both report 1.0, 2.0.

MWhen should you add a new API version instead of changing the existing one?design

Only for breaking changes. If the change can be additive, such as adding firstName and lastName next to fullName, do that: old clients keep working and there is one version to maintain. Add a version when a field must be removed, renamed, or change meaning, or a response must be reshaped.

SA field was renamed in the API and every installed mobile app now shows blank names. What do you do?scenario

Restore the old field at once (or serve the old shape again) so installed apps work. Move the rename into a new version that new app releases use. Add a rule and a review step: no field is removed or renamed without a new version and a deprecation date, and add tests that pin the version 1 response shape.

How to answer:

Put users first (restore), then do the change properly (versioning), then prevent it (rules and contract tests).

SHow do you retire an old API version safely?design

Mark it deprecated (so responses list it in api-deprecated-versions) and publish a date; log which version each request uses and contact the clients still on it; after the date, remove it and answer its routes with a clear error. For a school, one full academic year is a sensible notice period.

How to answer:

Cover announcing, measuring usage, communicating, and the final removal, with a realistic timeline.

AYour platform has many APIs used by internal and partner apps. How would you set a versioning policy?design

One style for all APIs, semantic rules for what counts as breaking, and a preference for additive changes. Breaking changes get a new major version with a published support period (for example, at least 12 months), deprecation headers, per-version documentation, and usage dashboards per client. Contract tests guard each supported version, and an API gateway can route versions to different services if needed.

How to answer:

Cover the style, the definition of breaking, support periods, communication, documentation, enforcement with tests, and how clients are tracked.

Background jobs

FWhat is a hosted service in ASP.NET Core, and what is BackgroundService?concept

A hosted service is a class the app starts when it starts and stops when it stops (IHostedService, registered with AddHostedService). BackgroundService is a base class for one: you override ExecuteAsync, which runs in the background for the life of the app, and its stoppingToken is cancelled at shutdown.

FWhich work should move out of a request into the background?concept

Work the user's next step does not depend on, especially slow work or work that calls another service: sending messages, emails, and reports. In the SMS, saving a payment stays in the request; the receipt message is queued. Moving it also stops a slow or failing provider from failing the payment itself.

MHow do you pass work from a controller to a background worker in ASP.NET Core?coding

Register a queue as a singleton, for example a bounded Channel of messages. The controller writes a message and returns at once; a BackgroundService reads the channel with ReadAllAsync and processes each message. Bounded with FullMode.Wait means a flood of work waits for room instead of using up memory.

MYour BackgroundService needs a scoped repository. How do you use it?coding

Not through the constructor: a hosted service is a singleton, and the app refuses to start with "Cannot consume scoped service ... from singleton IHostedService". Inject IServiceScopeFactory, create a scope for each piece of work (CreateAsyncScope), and resolve the repository from that scope; disposing the scope cleans it up.

MWhat happens if ExecuteAsync throws an unhandled exception, and how do you guard against it?scenario

Since .NET 6 the default BackgroundServiceExceptionBehavior is StopHost: the whole app shuts down, every endpoint included. Catch exceptions inside the loop (except cancellation at shutdown), log them, and carry on with the next message or the next scheduled run.

MHow would you write and test a job that must run every day at 18:00?coding

A BackgroundService loop: get the time from TimeProvider, work out the next 18:00, await Task.Delay(next - now, timeProvider, stoppingToken), run, repeat. Because the clock is injected, a test uses FakeTimeProvider: set it to 17:59, start the job, Advance one minute, and check the job ran, without waiting.

SThe API runs on two servers and both run the evening reminder job. How do you stop duplicate reminders?scenario

Let the database decide. Record each reminder in a table with a unique key on (invoice, date) before sending, and send only if the insert succeeded; the second server gets a duplicate-key error and skips that invoice. Alternatives are a distributed lock (such as sp_getapplock) or a scheduler that runs each job on one server.

How to answer:

Name the problem (every instance runs every hosted service), then give an idempotent design and say why it needs no coordination between servers.

SWhy is an in-memory queue risky, and what is the outbox pattern?design

Messages waiting in memory are lost when the app stops or crashes. With an outbox, the message is saved in a table in the same transaction as the business change (such as the payment); a worker reads unsent rows, sends them, and marks them sent. Nothing is lost on restart, but sending is at least once, so receivers must handle duplicates.

How to answer:

Explain what is lost and when, describe the outbox steps, and mention the at-least-once trade-off.

AHow would you design background processing for a school platform that sends millions of messages a day?design

Separate the API from the workers: the API writes messages to an outbox table or a durable broker (Azure Service Bus, RabbitMQ), and a scalable pool of worker services sends them. Add retries with backoff, a dead-letter queue for messages that keep failing, idempotency keys so retries never double-send, rate limits per provider, and dashboards for queue length and failures. Schedules run in one place (Hangfire, Quartz.NET, or a cloud scheduler) so each job runs once.

How to answer:

Cover durability, scaling the workers, retries and dead letters, idempotency, scheduling, and monitoring.

FWhen should an endpoint answer 202 Accepted instead of 200 OK?concept

When it has accepted the work but not finished it, because the work continues in the background. 200 says "done, here is the result"; 202 says "accepted, it will be done". In the SMS, POST /api/fee-reminders answers 202 with how many reminders were queued; the worker sends them afterwards.

Health checks

FWhat is a health check endpoint, and who calls it?concept

An address, such as /health/ready, where the API reports whether it is working. Load balancers call it every few seconds to decide which servers get traffic, container platforms use it to restart or pause instances, and monitoring tools use it to alert the team.

FWhat is the difference between a liveness check and a readiness check?concept

Liveness asks "is the process running?" and failing it usually means restart the process. Readiness asks "can it do its work now?" (database reachable, workers running) and failing it means send no traffic until it recovers. A database problem belongs in readiness, because restarting the API does not fix the database.

FWhat do Healthy, Degraded, and Unhealthy mean, and which HTTP codes do they return?concept

Healthy: everything works (200). Degraded: it works but needs attention, such as a growing queue (200, so traffic keeps flowing). Unhealthy: it cannot do its job, such as the database being unreachable (503).

MHow do you write a custom health check for SQL Server in ASP.NET Core?coding

Implement IHealthCheck: open a connection, run SELECT 1, return HealthCheckResult.Healthy; catch exceptions (except cancellation) and return Unhealthy with the exception, so it is logged. Register it with AddHealthChecks().AddCheck("database", tags: ["ready"], timeout: ...) and map it with MapHealthChecks.

MYour health endpoint returns 401 behind the load balancer and every server is marked down. Why, and what do you change?scenario

A fallback authorization policy requires a token on every endpoint, and the load balancer sends none. Mark the health endpoints AllowAnonymous, keep their body free of secrets and exception details, and also exclude them from rate limiting, because a 429 looks just as dead.

MHow can a health check tell that a BackgroundService has stopped?coding

BackgroundService.ExecuteTask is the running ExecuteAsync. Hosted services are singletons, so the check can ask for IEnumerable of IHostedService, find the worker, and report Unhealthy if ExecuteTask is completed while the app is running. Queue length and the last run's result can add Degraded warnings.

SThe database failed over for 30 seconds and every API instance restarted, causing a longer outage. What went wrong?scenario

The database check was in the liveness probe, so the platform restarted every instance, and they were still starting when the database returned. Keep liveness trivial (the process answers) and put the database in readiness, which only removes instances from traffic. Add timeouts so a hanging check fails fast.

How to answer:

Separate the two probe types, explain what each one triggers, and show why restarts cannot fix a dependency.

SShould the readiness check include every external service the API uses?design

No. Include what the instance itself needs and can be fixed per instance, such as its database connection. A shared external service (an SMS provider, a payment gateway) failing would mark every instance unready at once and turn a partial problem into a full outage. Monitor those separately and degrade gracefully.

How to answer:

Classify dependencies as critical per instance versus shared, and explain the blast radius of each choice.

ADesign health checking and monitoring for an API running many instances across regions.design

Per instance: a trivial liveness probe and a readiness probe with fast, time-limited checks of its own dependencies, answering only a status publicly and details on an internal address. Per service: synthetic checks from outside that exercise real flows, dashboards of error rates and latency, and alerts with clear owners. Health results can also be pushed (IHealthCheckPublisher) to the monitoring system, and shared dependencies are watched centrally rather than in every probe.

How to answer:

Cover probe design, timeouts, security of details, external synthetic checks, metrics and alerting, and shared-dependency handling.

Testing the API

FWhat is the difference between unit tests, integration tests, and API tests?concept

Unit tests check one class with fakes instead of a database, and run in milliseconds. Integration tests check code together with a real dependency, such as SQL that runs against SchoolDb. API tests run the whole application in memory (WebApplicationFactory) and send real HTTP requests through routing, security, validation, and the database.

FWhat is the test pyramid?concept

A guideline for the shape of a test suite: many fast unit tests at the bottom, fewer integration tests in the middle, and fewer still slow end-to-end or API tests at the top. Fast tests give quick feedback and pinpoint failures; the slower ones prove the parts work together.

FWhat does code coverage measure, and what does it not tell you?concept

The share of code lines (or branches) that ran during the tests. It does not say the lines were checked: a test without asserts still covers code. Use it to find code no test touches, not as proof of quality.

MHow can a test catch a missing dependency injection registration for any controller?coding

Find every controller type in the API assembly and create each one in a DI scope with ActivatorUtilities.CreateInstance, the way MVC does. Collect the exceptions and fail with their messages. It catches the mistake even though the app starts normally, because controllers are only created when a request reaches them.

MHow would you test that no endpoint is accidentally open to anonymous users?coding

Read the endpoints from EndpointDataSource in a WebApplicationFactory, select those with IAllowAnonymous metadata, and compare them with an approved list in the test. With a fallback policy that requires sign-in, every other endpoint is locked; a newly opened one fails the test until someone approves it.

MYour coverage jumped from 60% to 77% after a settings change, with no new tests. What do you check?scenario

The number of lines counted. Excluding CompilerGeneratedAttribute, for example, removes every async method's generated state machine, so most async code disappears from the report. Exclude only truly generated files (such as obj/), and treat any change in coverage without a change in tests as suspicious.

SWhat is approval testing, and how can it protect a public API?design

The test produces output (here, the sorted list of routes with versions) and compares it with an approved file in source control. Any difference fails the test and writes a received file; if the change is intended, the developer replaces the approved file and the change appears in code review. It stops routes being removed or renamed by accident, which would break installed clients.

How to answer:

Define the technique, give a concrete use, and explain how it turns accidental changes into reviewed ones.

SAPI tests share one real database. How do you keep them reliable?design

Each test creates its own data and removes it in finally, or works on data no other test changes; tests that touch the same rows share an xUnit collection so they never run at the same time. Avoid depending on today's date (inject TimeProvider) and on counts that other tests change. Alternatives are a transaction rolled back per test, a tool that resets tables (such as Respawn), or a fresh database container per run.

How to answer:

Cover isolation, cleanup, parallel runs, time, and the main reset strategies with their trade-offs.

ADesign a testing strategy for a product team of 40 developers working on one large API.design

A pyramid with clear ownership: fast unit tests on every save, integration and API tests in CI on every pull request against containerised dependencies, and contract tests (approved routes, OpenAPI diffs, consumer contracts) to protect clients. Add whole-API safety nets for security and DI, coverage reports per pull request to spot untested changes, flaky-test tracking with quarantine, time budgets for the suite, and smoke tests after each deployment.

How to answer:

Cover layers and speed, CI gates, contracts with clients, security nets, coverage use, flaky tests, and post-deployment checks.

CORS

FWhat is an origin, and when do two addresses have the same origin?concept

An origin is the scheme, host, and port of an address, such as http://localhost:5173. Two addresses have the same origin only when all three match; the path does not count. A page on port 5173 calling an API on port 5080 is a cross-origin call.

FWhat is CORS, and who enforces it?concept

Cross-Origin Resource Sharing. Browsers stop a page from reading answers from another origin unless the server allows it with the Access-Control-Allow-Origin header. The browser enforces it; curl, Postman, and other servers ignore it, so the API must still check tokens and permissions itself.

FA page shows 'Failed to fetch' but the same request works in Postman. How do you find out why?scenario

Open the browser's console and Network tab. A CORS refusal is explained in the console, for example "No 'Access-Control-Allow-Origin' header is present". The page's code only gets "Failed to fetch", on purpose, so the console is where the reason is.

MWhat is a preflight request, and when does the browser send one?concept

An OPTIONS request the browser sends before the real one, carrying Origin, Access-Control-Request-Method, and Access-Control-Request-Headers. It is sent for requests that are not simple: an Authorization header, a JSON body, or methods like PUT and DELETE. Without permission in the answer, the real request is never sent.

MWhere does UseCors go in the ASP.NET Core pipeline, and what breaks if it is too late?coding

Before UseAuthentication and UseAuthorization. The preflight carries no token, so if authorization runs first, the preflight gets 401 with no CORS headers and every browser call fails. In the SMS, moving it after UseAuthorization turned the preflight's 204 into a 401.

MAn unknown website's preflight gets 204 from your API. Is the API open to it?scenario

No. ASP.NET Core answers the preflight with 204 either way. What matters is the missing Access-Control-Allow-Origin header: without it the browser refuses, and the real request is never sent. Test for the header, not the status code.

SHow would you configure CORS for an API used by several websites across environments?design

One named policy with exact origins read from configuration: empty by default, the local front end in Development, the real websites per server. Validate the origins at startup (no path, no trailing slash), allow only the methods and headers the front ends use, expose only the response headers they must read, set a preflight max age, and cover it with tests that send preflights from allowed and unknown origins.

How to answer:

Cover exact origins from configuration, startup validation, least privilege for methods and headers, exposed headers, caching, and tests.

SWhy is AllowAnyOrigin risky, and when is it acceptable?concept

It lets any website read the API's answers inside your users' browsers, which matters most when the browser sends credentials automatically, such as cookies; ASP.NET Core refuses to combine it with AllowCredentials. It is acceptable for truly public, read-only data with no credentials, such as a public book catalogue, in a separate policy on those endpoints only.

How to answer:

Explain who is protected, the credentials rule, and a narrow per-endpoint policy for public data.

AMany teams' front ends call your organisation's APIs. How do you keep CORS correct and safe at scale?design

Avoid cross-origin calls where you can: serve the front end and API under one origin through a gateway or reverse proxy, or use a backend-for-frontend. Where CORS is needed, manage the allowed origins centrally per environment, validate them in CI and at startup, keep policies identical across environments except for origins, and test preflights in the pipeline. Never treat CORS as authentication; tokens and authorization stay on every API.

How to answer:

Cover same-origin options, central origin management, validation, environment parity, tests, and the limits of CORS as a security control.

Calling other services

FWhy should you not create a new HttpClient for every request?concept

Each new HttpClient opens new network connections, and closed ones linger for a while, so a busy server can run out of them. One HttpClient kept forever has the opposite problem: it never notices a changed DNS address. IHttpClientFactory reuses connections and renews them every two minutes by default.

FWhich HTTP failures are worth retrying, and which are not?concept

Retry errors that can pass: 5xx answers, 408, 429, timeouts, and broken connections. Do not retry 400, 401, 403, 404, 409, or 422: the request itself is wrong and will fail the same way again.

FWhat is a typed client?coding

A class that takes an HttpClient in its constructor and knows one service's API, registered with AddHttpClient<TInterface, TImplementation>(). The factory creates its HttpClient with the address, time limits, and retries set in one place, and the rest of the app only sees the interface.

MWhat are backoff and jitter, and why use both?concept

Backoff makes each wait longer than the last (about 1, 2, 4 seconds), so a struggling service gets time to recover. Jitter adds a random part to each wait, so many clients that failed together do not all retry at the same moment and overload the service again.

MWhat is an idempotency key, and when do you need one?concept

A unique id for one operation, sent in a header such as Idempotency-Key, the same on every attempt. If the service already handled that key, it returns the first result instead of doing the work again. You need it whenever you retry a request that changes something, such as a POST that sends a text or takes a payment, because a lost answer would otherwise cause a duplicate.

MExplain the three states of a circuit breaker.concept

Closed: calls go through and failures are counted. Open: after too many failures, every call fails at once without touching the service, for a break period. Half-open: after the break, one trial call goes through; success closes the circuit, failure opens it again.

SHow would you make an API's calls to a payment or messaging provider reliable?design

A typed client from IHttpClientFactory behind an interface; a time limit per attempt and a total limit; retries only for transient errors, with backoff and jitter; an idempotency key on every changing request; a circuit breaker; logs and metrics for retries and circuit events. Work that must not be lost goes into an outbox table first and is sent by a background job that can retry later. Tests replace the network with a scripted handler.

How to answer:

Cover the client, time limits, retry rules, idempotency, the circuit breaker, durable storage, observability, and testing.

SYour retries work, but during a one-hour provider outage messages were lost. What do you change?scenario

Retries cover seconds, not hours. Store each message durably before sending, in an outbox table with a status, in the same transaction as the business change. A background job sends pending rows, marks them sent, and retries failures later with growing delays, keeping the idempotency key, and alerts after too many failures.

How to answer:

Separate short retries from durable delivery, then describe the outbox, its job, and monitoring.

AMany services in your company call each other. How do you keep failures from spreading?design

Give every call a time budget shorter than the caller's own, and pass deadlines along. Retry only at one layer, so retries do not multiply through the chain, and cap them with retry budgets. Use circuit breakers and bulkheads (separate limits per dependency), idempotency keys for changing calls, and asynchronous messaging with durable queues where an immediate answer is not needed. Keep shared dependencies out of readiness checks, and watch retry and circuit metrics on dashboards.

How to answer:

Cover time budgets, where to retry, retry budgets, circuit breakers and bulkheads, idempotency, async messaging, health checks, and monitoring.

Files

FHow does an ASP.NET Core controller receive an uploaded file?coding

The client sends a multipart form, and the file part arrives as an IFormFile parameter whose name matches the form field. IFormFile gives the length, the claimed file name and content type, and OpenReadStream() to read the bytes.

FWhich parts of an uploaded file can you trust?concept

Only what actually arrives: its size, and its content after you check it. The file name, extension, and content type are chosen by the user's computer, so never use the name to save the file, and check the content itself, for example the header row of a CSV or the first bytes of a picture.

FHow do you make the browser download an API's answer as a file?coding

Return the bytes with the right content type and a Content-Disposition: attachment; filename=... header. In ASP.NET Core, File(...), PhysicalFile(...), or Results.Stream(..., contentType, fileDownloadName) set it from the file name you pass.

MHow do you limit the size of uploads, and what does the client get when a file is too big?coding

[RequestSizeLimit] on the action (or Kestrel's MaxRequestBodySize) stops reading beyond the limit, and form options limit multipart bodies. In an MVC action, the failed form read becomes a 400 "Request body too large" before the action runs. Check IFormFile.Length in code as well, since a test server may not enforce the limit.

MWhy might Validator.TryValidateObject miss the rules of a request record?concept

For a positional record, ASP.NET Core requires validation attributes on the constructor parameters, and throws if they are on the properties. Validator reads only properties, so it does not see them. Outside MVC you must read the parameters' attributes yourself, as the SMS's RecordValidator does, or validate differently.

MHow do you stream a large download instead of building it in memory?coding

Read rows one at a time (for example Dapper's QueryUnbufferedAsync as an IAsyncEnumerable) and write each to the response stream as it arrives, for example inside Results.Stream. The response goes out chunked, memory stays flat, and the client starts receiving data at once.

SDesign a CSV import of thousands of rows that must be safe to repeat and must never save half a file.design

Check the file (size, header), then every row with the same rules as the single-record endpoint, collecting errors by row. Bulk-copy valid rows into a staging table, check them against existing data, and apply one MERGE in a single transaction; any problem rolls everything back and returns every error. Matching on a natural key makes a repeated upload change nothing. Return counts of added, updated, and unchanged rows.

How to answer:

Cover the two stages of checks, error reporting by row, staging plus bulk copy, one transaction, the natural key for idempotency, and the summary.

SWhat security risks do file uploads and CSV exports bring, and how do you handle them?concept

Uploads: huge files (limit size), path traversal through file names (never use them; generate names), lying types (check content and signatures), malware (scan files that are stored or shared), and public folders (store outside them and serve through an endpoint that checks permissions). Exports: CSV injection, where a value starting with =, +, -, or @ runs as a formula; neutralise such values. Both need authorization.

How to answer:

Split upload and export risks, and give a defence for each.

AUsers upload large files (videos, scans) to your platform. How would you design it?design

Do not stream large files through the API servers. The API authorises the upload and returns a short-lived signed URL to object storage; the client uploads directly, in resumable chunks. A storage event triggers background processing: virus scan, type check by content, thumbnails or conversion, then the metadata row is marked ready. Downloads use signed URLs or a CDN. Set size and type limits, quotas per tenant, retention rules, and encryption at rest.

How to answer:

Cover direct-to-storage uploads with signed URLs, resumable chunks, asynchronous processing and scanning, metadata, downloads through a CDN, limits and quotas, and data protection.