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.
See the example · Lesson 2: Client, API and database · Permalink
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.
See the example · Lesson 2: Client, API and database · Permalink
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.
See the example · Lesson 2: Client, API and database · Permalink
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.
See the example · Lesson 2: Client, API and database · Permalink
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.
See the example · Lesson 2: Client, API and database · Permalink
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.
See the example · Lesson 2: Client, API and database · Permalink
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.
See the example · Lesson 65: HTTP and REST basics · Permalink
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".
See the example · Lesson 65: HTTP and REST basics · Permalink
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.
See the example · Lesson 65: HTTP and REST basics · Permalink
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.
See the example · Lesson 65: HTTP and REST basics · Permalink
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".
See the example · Lesson 65: HTTP and REST basics · Permalink
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.
See the example · Lesson 65: HTTP and REST basics · Permalink
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.
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.
See the example · Lesson 65: HTTP and REST basics · Permalink
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.
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.
See the example · Lesson 65: HTTP and REST basics · Permalink
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.
Connect statelessness to horizontal scaling, resilience, and caching, and say where state goes instead: the client, tokens, the database, or a distributed cache.
See the example · Lesson 65: HTTP and REST basics · Permalink
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.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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().
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
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.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
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.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
Cover hosting options with trade-offs, then a checklist of what changes between environments, and how configuration is supplied per environment.
See the example · Lesson 66: Creating SchoolApp.Api · Permalink
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.
See the example · Lesson 67: Middleware pipeline · Permalink
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.)
See the example · Lesson 67: Middleware pipeline · Permalink
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.
See the example · Lesson 67: Middleware pipeline · Permalink
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().
See the example · Lesson 67: Middleware pipeline · Permalink
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.
See the example · Lesson 67: Middleware pipeline · Permalink
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.
See the example · Lesson 67: Middleware pipeline · Permalink
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.
Go from symptom to the pipeline, explain the dependency between the two steps, and add the regression test so the order is protected.
See the example · Lesson 67: Middleware pipeline · Permalink
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.
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.
See the example · Lesson 67: Middleware pipeline · Permalink
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.
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.
See the example · Lesson 67: Middleware pipeline · Permalink
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.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
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.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
Give the structure, the grouping feature, and how it improves merges and tests; note that switching to controllers is not required to fix it.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
Make it a decision with criteria and a written record, and show that a layered design limits the cost of changing it later.
See the example · Lesson 68: Controllers vs minimal APIs · Permalink
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
Explain why the bug appears only under concurrency, list the suspicious registration patterns, and give the fix and a way to prove it.
See the example · Lesson 69: Dependency injection · Permalink
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.
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
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.
See the example · Lesson 69: Dependency injection · Permalink
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
See the example · Lesson 73: Model binding and validation · Permalink
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.
See the example · Lesson 73: Model binding and validation · Permalink
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.
See the example · Lesson 73: Model binding and validation · Permalink
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.
See the example · Lesson 73: Model binding and validation · Permalink
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.
See the example · Lesson 73: Model binding and validation · Permalink
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.
See the example · Lesson 73: Model binding and validation · Permalink
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.
Explain what each layer can see, give SMS examples for each, and how you keep messages consistent, such as mapping domain errors to ProblemDetails.
See the example · Lesson 73: Model binding and validation · Permalink
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.
Identify the ambiguity, enforce a strict contract, communicate it in the OpenAPI document, and plan the data repair.
See the example · Lesson 73: Model binding and validation · Permalink
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.
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.
See the example · Lesson 73: Model binding and validation · Permalink
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.
See the example · Lesson 74: Responses and status codes · Permalink
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.
See the example · Lesson 74: Responses and status codes · Permalink
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.
See the example · Lesson 74: Responses and status codes · Permalink
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.
See the example · Lesson 74: Responses and status codes · Permalink
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.
See the example · Lesson 74: Responses and status codes · Permalink
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.
See the example · Lesson 74: Responses and status codes · Permalink
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.
Explain status codes as instructions for client behaviour, give the mapping, and mention idempotency keys for safe retries of money operations.
See the example · Lesson 74: Responses and status codes · Permalink
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.
Contrast the two approaches with an example, and mention testing the mapping once and keeping the domain free of HTTP concepts.
See the example · Lesson 74: Responses and status codes · Permalink
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.
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.
See the example · Lesson 74: Responses and status codes · Permalink
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
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.
See the example · Lesson 80: API response wrapper · Permalink
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.
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.
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.
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.
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.
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.
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.
See the example · Lesson 77: Teachers and guardians · Permalink
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 }).
See the example · Lesson 77: Teachers and guardians · Permalink
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.
See the example · Lesson 77: Teachers and guardians · Permalink
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.
See the example · Lesson 77: Teachers and guardians · Permalink
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.
See the example · Lesson 77: Teachers and guardians · Permalink
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")].
See the example · Lesson 77: Teachers and guardians · Permalink
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.
Cover the code fix, the data repair with its decision rule and verification, and the database constraint as the lasting guard.
See the example · Lesson 77: Teachers and guardians · Permalink
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.
Mention data minimisation, roles and authorization (Module 12), logging and caching risks of personal data, and reviewing DTOs as part of design.
See the example · Lesson 77: Teachers and guardians · Permalink
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.
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.
See the example · Lesson 77: Teachers and guardians · Permalink
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.
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.
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.
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.
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.
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.
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}.
See the example · Lesson 82: Exams and report cards · Permalink
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).
See the example · Lesson 82: Exams and report cards · Permalink
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.
See the example · Lesson 82: Exams and report cards · Permalink
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.
See the example · Lesson 82: Exams and report cards · Permalink
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.
See the example · Lesson 82: Exams and report cards · Permalink
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.
See the example · Lesson 82: Exams and report cards · Permalink
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.
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.
See the example · Lesson 82: Exams and report cards · Permalink
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.
Explain defence in depth, who writes to the database besides the API, and how to keep both checks consistent for clients.
See the example · Lesson 82: Exams and report cards · Permalink
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.
Separate stored data from rules, show how rules vary per school, and cover versioning, reproducibility, and testing.
See the example · Lesson 82: Exams and report cards · Permalink
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.
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.
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.
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.
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.
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.
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.
See the example · Lesson 81: Switching to Dapper · Permalink
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.
See the example · Lesson 81: Switching to Dapper · Permalink
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.
See the example · Lesson 81: Switching to Dapper · Permalink
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>().
See the example · Lesson 81: Switching to Dapper · Permalink
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.
See the example · Lesson 81: Switching to Dapper · Permalink
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.
See the example · Lesson 81: Switching to Dapper · Permalink
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.
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.
See the example · Lesson 81: Switching to Dapper · Permalink
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.
Cover automated tests, side-by-side comparison of real responses, write paths, performance, and a safe rollout with the old code still available.
See the example · Lesson 81: Switching to Dapper · Permalink
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.
Separate the non-negotiable rules from the tool choice, give criteria for choosing, and explain how you support teams and enforce the rules.
See the example · Lesson 81: Switching to Dapper · Permalink
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.
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.
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.
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.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
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.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
Order the response: contain, clean up existing data, notify, then prevent with design and tests.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
Cover the three signals (logs, traces, metrics), how they are linked, what you alert on, and the privacy and cost limits.
See the example · Lesson 90: Logging and trace ids · Permalink
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.
See the example · Lesson 91: Server-side caching · Permalink
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.
See the example · Lesson 91: Server-side caching · Permalink
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.
See the example · Lesson 91: Server-side caching · Permalink
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.
See the example · Lesson 91: Server-side caching · Permalink
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.
See the example · Lesson 91: Server-side caching · Permalink
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.
See the example · Lesson 91: Server-side caching · Permalink
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?
Identify the stale cache as the cause, decide whether the data should be cached at all, and describe invalidation and review rules.
See the example · Lesson 91: Server-side caching · Permalink
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.
Explain per-server memory caches, the visible effect, and compare the fixes by freshness, cost, and complexity.
See the example · Lesson 91: Server-side caching · Permalink
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.
Cover isolation between tenants, the cache levels, what is cached and for how long, invalidation, and monitoring.
See the example · Lesson 91: Server-side caching · Permalink
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
Define the technique, give a concrete use, and explain how it turns accidental changes into reviewed ones.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
Cover isolation, cleanup, parallel runs, time, and the main reset strategies with their trade-offs.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
Cover layers and speed, CI gates, contracts with clients, security nets, coverage use, flaky tests, and post-deployment checks.
See the example · Lesson 96: Tests for the whole API · Permalink
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.
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.
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.
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.
See the example · Extra 8: Calling other services · Permalink
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.
See the example · Extra 8: Calling other services · Permalink
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.
See the example · Extra 8: Calling other services · Permalink
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.
See the example · Extra 8: Calling other services · Permalink
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.
See the example · Extra 8: Calling other services · Permalink
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.
See the example · Extra 8: Calling other services · Permalink
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.
Cover the client, time limits, retry rules, idempotency, the circuit breaker, durable storage, observability, and testing.
See the example · Extra 8: Calling other services · Permalink
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.
Separate short retries from durable delivery, then describe the outbox, its job, and monitoring.
See the example · Extra 8: Calling other services · Permalink
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.
Cover time budgets, where to retry, retry budgets, circuit breakers and bulkheads, idempotency, async messaging, health checks, and monitoring.
See the example · Extra 8: Calling other services · Permalink
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.
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.
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.
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.